jsonToGo

try to take over the world!

当前为 2020-05-05 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         jsonToGo
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://*/*
// @match        http://*/*
// @grant        none
// ==/UserScript==

function jsonToGo(json, typename, flatten = true)
{
	let data;
	let scope;
	let go = "";
	let tabs = 0;
    let typeNames = []

	const seen = {};
	const stack = [];
	let accumulator = "";
	let innerTabs = 0;
	let parent = "";

	try
	{
		data = JSON.parse(json.replace(/:(\s*\d*)\.0/g, ":$1.1")); // hack that forces floats to stay as floats
		scope = data;
	}
	catch (e)
	{
		return {
			go: "",
			error: e.message
		};
	}

	typename = format(typename || "AutoGenerated");
	append(`type ${typename} `);

	parseScope(scope);
    const result = flatten ? go += accumulator : go
    console.log(result)
    return

	function parseScope(scope, depth = 0)
	{
		if (typeof scope === "object" && scope !== null)
		{
			if (Array.isArray(scope))
			{
				let sliceType;
				const scopeLength = scope.length;

				for (let i = 0; i < scopeLength; i++)
				{
					const thisType = goType(scope[i]);
					if (!sliceType)
						sliceType = thisType;
					else if (sliceType != thisType)
					{
						sliceType = mostSpecificPossibleGoType(thisType, sliceType);
						if (sliceType == "interface{}")
							break;
					}
				}

				const slice = flatten && ["struct", "slice"].includes(sliceType)
					? `[]${parent}`
					: `[]`;

				if (flatten && depth >= 2)
					appender(slice);
				else
					append(slice)
				if (sliceType == "struct") {
					const allFields = {};

					// for each field counts how many times appears
					for (let i = 0; i < scopeLength; i++)
					{
						const keys = Object.keys(scope[i])
						for (let k in keys)
						{
							let keyname = keys[k];
							if (!(keyname in allFields)) {
								allFields[keyname] = {
									value: scope[i][keyname],
									count: 0
								}
							}
							else {
								const existingValue = allFields[keyname].value;
								const currentValue = scope[i][keyname];

								if (compareObjects(existingValue, currentValue)) {
									const comparisonResult = compareObjectKeys(
										Object.keys(currentValue),
										Object.keys(existingValue)
									)
									if (!comparisonResult) {
										keyname = `${keyname}_${uuidv4()}`;
										allFields[keyname] = {
											value: currentValue,
											count: 0
										};
									}
								}
							}
							allFields[keyname].count++;
						}
					}

					// create a common struct with all fields found in the current array
					// omitempty dict indicates if a field is optional
					const keys = Object.keys(allFields), struct = {}, omitempty = {};
					for (let k in keys)
					{
						const keyname = keys[k], elem = allFields[keyname];

						struct[keyname] = elem.value;
						omitempty[keyname] = elem.count != scopeLength;
					}
					parseStruct(depth + 1, innerTabs, struct, omitempty); // finally parse the struct !!
				}
				else if (sliceType == "slice") {
					parseScope(scope[0], depth)
				}
				else {
					if (flatten && depth >= 2) {
						appender(sliceType || "interface{}");
					} else {
						append(sliceType || "interface{}");
					}
				}
			}
			else
			{
				if (flatten) {
					if (depth >= 2){
						appender(parent)
					}
					else {
						append(parent)
					}
				}
				parseStruct(depth + 1, innerTabs, scope);
			}
		}
		else {
			if (flatten && depth >= 2){
				appender(goType(scope));
			}
			else {
				append(goType(scope));
			}
		}
	}

	function parseStruct(depth, innerTabs, scope, omitempty)
	{
		if (flatten) {
			stack.push(
				depth >= 2
				? "\n"
				: ""
			)
		}

		if (flatten && depth >= 2)
		{
			const parentType = `type ${parent}`;
			const scopeKeys = formatScopeKeys(Object.keys(scope));

			// this can only handle two duplicate items
			// future improvement will handle the case where there could
			// three or more duplicate keys with different values
			if (parent in seen && compareObjectKeys(scopeKeys, seen[parent])) {
				stack.pop();
				return
			}
			seen[parent] = scopeKeys;

			appender(`${parentType} struct {\n`);
			++innerTabs;
			const keys = Object.keys(scope);
			for (let i in keys)
			{
				const keyname = getOriginalName(keys[i]);
				indenter(innerTabs)
				const typename = format(keyname)
				appender(typename+" ");
				parent = typename
				parseScope(scope[keys[i]], depth);
				appender(' `json:"'+keyname);
				if (omitempty && omitempty[keys[i]] === true)
				{
					appender(',omitempty');
				}
				appender('"`\n');
			}
			indenter(--innerTabs);
			appender("}");
		}
		else
		{
			append("struct {\n");
			++tabs;
			const keys = Object.keys(scope);
			for (let i in keys)
			{
				const keyname = getOriginalName(keys[i]);
				indent(tabs);
				const typename = format(keyname);
				append(typename+" ");
				parent = typename
				parseScope(scope[keys[i]], depth);
				append(' `json:"'+keyname);
				if (omitempty && omitempty[keys[i]] === true)
				{
					append(',omitempty');
				}
				append('"`\n');
			}
			indent(--tabs);
			append("}");
		}
		if (flatten)
			accumulator += stack.pop();
	}

	function indent(tabs)
	{
		for (let i = 0; i < tabs; i++)
			go += '\t';
	}

	function append(str)
	{
		go += str;
	}

	function indenter(tabs)
	{
		for (let i = 0; i < tabs; i++)
			stack[stack.length - 1] += '\t';
	}

	function appender(str)
	{
		stack[stack.length - 1] += str;
	}

	// Sanitizes and formats a string to make an appropriate identifier in Go
	function format(str)
	{
		if (!str)
			return "";
		else if (str.match(/^\d+$/))
			str = "Num" + str;
		else if (str.charAt(0).match(/\d/))
		{
			const numbers = {'0': "Zero_", '1': "One_", '2': "Two_", '3': "Three_",
				'4': "Four_", '5': "Five_", '6': "Six_", '7': "Seven_",
				'8': "Eight_", '9': "Nine_"};
			str = numbers[str.charAt(0)] + str.substr(1);
		}
		return toProperCase(str).replace(/[^a-z0-9]/ig, "") || "NAMING_FAILED";
	}

	// Determines the most appropriate Go type
	function goType(val)
	{
		if (val === null)
			return "interface{}";

		switch (typeof val)
		{
			case "string":
				if (/\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(\+\d\d:\d\d|Z)/.test(val))
					return "time.Time";
				else
					return "string";
			case "number":
				if (val % 1 === 0)
				{
					if (val > -2147483648 && val < 2147483647)
						return "int";
					else
						return "int64";
				}
				else
					return "float64";
			case "boolean":
				return "bool";
			case "object":
				if (Array.isArray(val))
					return "slice";
				return "struct";
			default:
				return "interface{}";
		}
	}

	// Given two types, returns the more specific of the two
	function mostSpecificPossibleGoType(typ1, typ2)
	{
		if (typ1.substr(0, 5) == "float"
				&& typ2.substr(0, 3) == "int")
			return typ1;
		else if (typ1.substr(0, 3) == "int"
				&& typ2.substr(0, 5) == "float")
			return typ2;
		else
			return "interface{}";
	}

	// Proper cases a string according to Go conventions
	function toProperCase(str)
	{
		// https://github.com/golang/lint/blob/5614ed5bae6fb75893070bdc0996a68765fdd275/lint.go#L771-L810
		const commonInitialisms = [
			"ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP",
			"HTTPS", "ID", "IP", "JSON", "LHS", "QPS", "RAM", "RHS", "RPC", "SLA",
			"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "UID", "UUID",
			"URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS"
		];

		return str.replace(/(^|[^a-zA-Z])([a-z]+)/g, function(unused, sep, frag)
		{
			if (commonInitialisms.indexOf(frag.toUpperCase()) >= 0)
				return sep + frag.toUpperCase();
			else
				return sep + frag[0].toUpperCase() + frag.substr(1).toLowerCase();
		}).replace(/([A-Z])([a-z]+)/g, function(unused, sep, frag)
		{
			if (commonInitialisms.indexOf(sep + frag.toUpperCase()) >= 0)
				return (sep + frag).toUpperCase();
			else
				return sep + frag;
		});
	}

	function uuidv4() {
		return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
		  var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
		  return v.toString(16);
		});
	}

	function getOriginalName(unique) {
		const reLiteralUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
		const uuidLength = 36;

		if (unique.length >= uuidLength) {
			const tail = unique.substr(-uuidLength);
			if (reLiteralUUID.test(tail)) {
				return unique.slice(0, -1 * (uuidLength + 1))
			}
		}
		return unique
	}

	function compareObjects(objectA, objectB) {
		const object = "[object Object]";
		return Object.prototype.toString.call(objectA) === object
			&& Object.prototype.toString.call(objectB) === object;
	}

	function compareObjectKeys(itemAKeys, itemBKeys) {
		const lengthA = itemAKeys.length;
		const lengthB = itemBKeys.length;

		// nothing to compare, probably identical
		if (lengthA == 0 && lengthB == 0)
			return true;

		// duh
		if (lengthA != lengthB)
			return false;

		for (let item of itemAKeys) {
			if (!itemBKeys.includes(item))
				return false;
		}
		return true;
	}

	function formatScopeKeys(keys) {
		for (let i in keys) {
			keys[i] = format(keys[i]);
		}
		return keys
	}
}

if (typeof module != 'undefined') {
    if (!module.parent) {
        if (process.argv.length > 2 && process.argv[2] === '-big') {
            bufs = []
            process.stdin.on('data', function(buf) {
                bufs.push(buf)
            })
            process.stdin.on('end', function() {
                const json = Buffer.concat(bufs).toString('utf8')
                console.log(jsonToGo(json).go)
            })
        } else {
            process.stdin.on('data', function(buf) {
                const json = buf.toString('utf8')
                console.log(jsonToGo(json).go)
            })
        }
    } else {
        module.exports = jsonToGo
    }
}

function jsonToTs (data) {
    if (typeof data === 'object') {
            if (typeof data.length !== 'undefined') {
                if (data.length > 0) {
                    return `Array<${jsonToTs(data[0])}>`
                } else {
                    return 'Array<any>'
                }
            }
            const keys = Object.keys(data)
            if (keys.length === 0) {
                return 'object'
            }
            let a = Object.keys(data).map(v => {
                const type = jsonToTs(data[v])
                return `${v}: ${type}`
            }).join(',\n')
            a = `{\n${a}\n}`
            return a
        } else {
            return typeof data
        }
}

(function() {
    'use strict';
    document.jsonToGo = jsonToGo
    document.jsonToTs = (data) => {
        return jsonToTs(JSON.parse(data))
    }
})();