- // ==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))
- }
- })();