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