diff --git a/README.md b/README.md index 35e85c9..015a7df 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,23 @@ Contributions are welcome! Open a pull request to fix a bug, or open an issue to node json-to-go.js sample.json ``` +- Read JSON file on Unix systems: + + ```sh + ./json-to-go.js sample.json + ``` + - Read JSON file from stdin: ```sh - node json-to-go.js < sample.json - cat sample.json | node json-to-go.js + ./json-to-go.js < sample.json + cat sample.json | ./json-to-go.js + ``` + +- For more options, check the help page + + ```sh + ./json-to-go.js --help ``` ### Credits diff --git a/json-to-go-v2.js b/json-to-go-v2.js new file mode 100755 index 0000000..d025779 --- /dev/null +++ b/json-to-go-v2.js @@ -0,0 +1,597 @@ +#!/usr/bin/env node +/* + JSON-to-Go v2 + by Matt Holt + + https://github.com/mholt/json-to-go + + A simple utility to translate JSON into a Go type definition. +*/ + +function jsonToGo(json, options = {}) { + const typename = options.typename === undefined || typeof options.typename !== "string" ? "AutoGenerated" : options.typename + const flatten = options.flatten === undefined ? true : options.flatten + const example = options.example === undefined ? false : options.example + const allOmitempty = options.allOmitempty === undefined ? false : options.allOmitempty + + let data; + let scope; + let go = ""; + let tabs = 0; + + const seen = {}; + const stack = []; + let accumulator = ""; + let innerTabs = 0; + let parent = ""; + let globallySeenTypeNames = []; + let previousParents = ""; + + try + { + data = JSON.parse(json.replace(/(:\s*\[?\s*-?\d*)\.0/g, "$1.1")); // hack that forces floats to stay as floats + scope = data; + } + catch (e) + { + return { + go: "", + error: e.message + }; + } + + append(`type ${format(typename)} `); + + parseScope(scope); + + if (flatten) + go += accumulator + + // add final newline for POSIX 3.206 + if (!go.endsWith(`\n`)) + go += `\n` + + return { + go: go + }; + + + 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 == "any") + 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 (!areSameType(existingValue, currentValue)) { + if(existingValue !== null) { + allFields[keyname].value = null // force type "any" if types are not identical + console.warn(`Warning: key "${keyname}" uses multiple types. Defaulting to type "any".`) + } + allFields[keyname].count++ + continue + } + + if (areObjects(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, previousParents); // finally parse the struct !! + } + else if (sliceType == "slice") { + parseScope(scope[0], depth) + } + else { + if (flatten && depth >= 2) { + appender(sliceType || "any"); + } else { + append(sliceType || "any"); + } + } + } + else + { + if (flatten) { + if (depth >= 2){ + appender(parent) + } + else { + append(parent) + } + } + parseStruct(depth + 1, innerTabs, scope, false, previousParents); + } + } + else { + if (flatten && depth >= 2){ + appender(goType(scope)); + } + else { + append(goType(scope)); + } + } + } + + function parseStruct(depth, innerTabs, scope, omitempty, oldParents) + { + if (flatten) { + stack.push( + depth >= 2 + ? "\n" + : "" + ) + } + + const seenTypeNames = []; + + 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); + previousParents = parent + for (let i in keys) + { + const keyname = getOriginalName(keys[i]); + indenter(innerTabs) + let typename + // structs will be defined on the top level of the go file, so they need to be globally unique + if (typeof scope[keys[i]] === "object" && scope[keys[i]] !== null) { + typename = uniqueTypeName(format(keyname), globallySeenTypeNames, previousParents) + globallySeenTypeNames.push(typename) + } else { + typename = uniqueTypeName(format(keyname), seenTypeNames) + seenTypeNames.push(typename) + } + + appender(typename+" "); + parent = typename + parseScope(scope[keys[i]], depth); + appender(' `json:"'+keyname); + if (allOmitempty || (omitempty && omitempty[keys[i]] === true)) + { + appender(',omitempty'); + } + appender('"`\n'); + } + indenter(--innerTabs); + appender("}"); + previousParents = oldParents; + } + else + { + append("struct {\n"); + ++tabs; + const keys = Object.keys(scope); + previousParents = parent + for (let i in keys) + { + const keyname = getOriginalName(keys[i]); + indent(tabs); + let typename + // structs will be defined on the top level of the go file, so they need to be globally unique + if (typeof scope[keys[i]] === "object" && scope[keys[i]] !== null) { + typename = uniqueTypeName(format(keyname), globallySeenTypeNames, previousParents) + globallySeenTypeNames.push(typename) + } else { + typename = uniqueTypeName(format(keyname), seenTypeNames) + seenTypeNames.push(typename) + } + + append(typename+" "); + parent = typename + parseScope(scope[keys[i]], depth); + append(' `json:"'+keyname); + if (allOmitempty || (omitempty && omitempty[keys[i]] === true)) + { + append(',omitempty'); + } + if (example && scope[keys[i]] !== "" && typeof scope[keys[i]] !== "object") + { + append('" example:"'+scope[keys[i]]) + } + append('"`\n'); + } + indent(--tabs); + append("}"); + previousParents = oldParents; + } + 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; + } + + // Generate a unique name to avoid duplicate struct field names. + // This function appends a number at the end of the field name. + function uniqueTypeName(name, seen, prefix=null) { + if (seen.indexOf(name) === -1) { + return name; + } + + // check if we can get a unique name by prefixing it + if(prefix) { + name = prefix+name + if (seen.indexOf(name) === -1) { + return name; + } + } + + let i = 0; + while (true) { + let newName = name + i.toString(); + if (seen.indexOf(newName) === -1) { + return newName; + } + + i++; + } + } + + // Sanitizes and formats a string to make an appropriate identifier in Go + function format(str) + { + str = formatNumber(str); + + let sanitized = toProperCase(str).replace(/[^a-z0-9]/ig, "") + if (!sanitized) { + return "NAMING_FAILED"; + } + + // After sanitizing the remaining characters can start with a number. + // Run the sanitized string again trough formatNumber to make sure the identifier is Num[0-9] or Zero_... instead of 1. + return formatNumber(sanitized) + } + + // Adds a prefix to a number to make an appropriate identifier in Go + function formatNumber(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 str; + } + + // Determines the most appropriate Go type + function goType(val) + { + if (val === null) + return "any"; + + 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 "any"; + } + } + + // 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 "any"; + } + + // Proper cases a string according to Go conventions + function toProperCase(str) + { + // ensure that the SCREAMING_SNAKE_CASE is converted to snake_case + if (str.match(/^[_A-Z0-9]+$/)) { + str = str.toLowerCase(); + } + + // 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 areObjects(objectA, objectB) { + const object = "[object Object]"; + return Object.prototype.toString.call(objectA) === object + && Object.prototype.toString.call(objectB) === object; + } + + function areSameType(objectA, objectB) { + // prototype.toString required to compare Arrays and Objects + const typeA = Object.prototype.toString.call(objectA) + const typeB = Object.prototype.toString.call(objectB) + return typeA === typeB + } + + 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' || !module.parent) { + let filename = null + let options = {} + + function jsonToGoWithErrorHandling(json, options) { + const output = jsonToGo(json, options) + if (output.error) { + console.error(output.error) + process.exitCode = 1 + } + process.stdout.write(output.go) + } + + function printHelp() { + const path = require('path'); + const scriptname = path.basename(process.argv[1]) + + console.log(`\ +Usage: ${scriptname} [OPTION]... [FILE] +Convert json to go file and prints the result on stdout. + +Optional arguments: + --help Print this help page + --all-omitempty Make all fields "omitempty" (Default: false) + --examples Add examples to go struct. Currently only works without flatten. (Default: false) + --flatten Flatten go struct (Default: true) + --typename=NAME Use a specific name for typename (Default: "AutoGenerated") + +All arguments can be inverted by specifing "--no-...", for example "--no-examples". + `) + } + + process.argv.forEach((val, index) => { + if (index < 2) + return + + if (!val.startsWith('-')) { + filename = val + return + } + + let argument = { + arg: val.split("=")[0].replace(/^-+/, ''), + value: val.split("=")[1] || true, + } + + if (argument.arg.startsWith("no-")) { + argument.arg = argument.arg.replace(/^no-/, '') + argument.value = !argument.value + } + + switch (argument.arg) { + case "big": + console.warn(`Warning: The argument '${argument}' has been deprecated and has no effect anymore`) + break + case "typename": + options.typename = argument.value + break + case "flatten": + options.flatten = argument.value + break + case "examples": + options.example = argument.value + break + case "all-omitempty": + options.allOmitempty = argument.value + break + case "help": + printHelp() + process.exit(0) + default: + console.error(`Unexpected argument ${val} received`) + printHelp() + process.exit(1) + } + }) + + if (filename) { + const fs = require('fs'); + const json = fs.readFileSync(filename, 'utf8'); + jsonToGoWithErrorHandling(json, options) + } + + if (!filename) { + bufs = [] + process.stdin.on('data', function (buf) { + bufs.push(buf) + }) + process.stdin.on('end', function () { + const json = Buffer.concat(bufs).toString('utf8') + jsonToGoWithErrorHandling(json, options) + }) + } +} + +if (typeof module !== 'undefined') { + module.exports = jsonToGo +} diff --git a/json-to-go.js b/json-to-go.js old mode 100644 new mode 100755 index 870ce0b..b650949 --- a/json-to-go.js +++ b/json-to-go.js @@ -1,548 +1,41 @@ +#!/usr/bin/env node /* JSON-to-Go by Matt Holt https://github.com/mholt/json-to-go - A simple utility to translate JSON into a Go type definition. + A backwards compatible integration of json-to-go-v2 */ -function jsonToGo(json, typename, flatten = true, example = false, allOmitempty = false) -{ - let data; - let scope; - let go = ""; - let tabs = 0; +const jsonToGov2 = require('./json-to-go-v2') - const seen = {}; - const stack = []; - let accumulator = ""; - let innerTabs = 0; - let parent = ""; - let globallySeenTypeNames = []; - let previousParents = ""; - - try - { - data = JSON.parse(json.replace(/(:\s*\[?\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); - - if (flatten) - go += accumulator - - // add final newline for POSIX 3.206 - if (!go.endsWith(`\n`)) - go += `\n` - - return { - go: go - }; - - - 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 == "any") - 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 (!areSameType(existingValue, currentValue)) { - if(existingValue !== null) { - allFields[keyname].value = null // force type "any" if types are not identical - console.warn(`Warning: key "${keyname}" uses multiple types. Defaulting to type "any".`) - } - allFields[keyname].count++ - continue - } - - if (areObjects(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, previousParents); // finally parse the struct !! - } - else if (sliceType == "slice") { - parseScope(scope[0], depth) - } - else { - if (flatten && depth >= 2) { - appender(sliceType || "any"); - } else { - append(sliceType || "any"); - } - } - } - else - { - if (flatten) { - if (depth >= 2){ - appender(parent) - } - else { - append(parent) - } - } - parseStruct(depth + 1, innerTabs, scope, false, previousParents); - } - } - else { - if (flatten && depth >= 2){ - appender(goType(scope)); - } - else { - append(goType(scope)); - } - } - } - - function parseStruct(depth, innerTabs, scope, omitempty, oldParents) - { - if (flatten) { - stack.push( - depth >= 2 - ? "\n" - : "" - ) - } - - const seenTypeNames = []; - - 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); - previousParents = parent - for (let i in keys) - { - const keyname = getOriginalName(keys[i]); - indenter(innerTabs) - let typename - // structs will be defined on the top level of the go file, so they need to be globally unique - if (typeof scope[keys[i]] === "object" && scope[keys[i]] !== null) { - typename = uniqueTypeName(format(keyname), globallySeenTypeNames, previousParents) - globallySeenTypeNames.push(typename) - } else { - typename = uniqueTypeName(format(keyname), seenTypeNames) - seenTypeNames.push(typename) - } - - appender(typename+" "); - parent = typename - parseScope(scope[keys[i]], depth); - appender(' `json:"'+keyname); - if (allOmitempty || (omitempty && omitempty[keys[i]] === true)) - { - appender(',omitempty'); - } - appender('"`\n'); - } - indenter(--innerTabs); - appender("}"); - previousParents = oldParents; - } - else - { - append("struct {\n"); - ++tabs; - const keys = Object.keys(scope); - previousParents = parent - for (let i in keys) - { - const keyname = getOriginalName(keys[i]); - indent(tabs); - let typename - // structs will be defined on the top level of the go file, so they need to be globally unique - if (typeof scope[keys[i]] === "object" && scope[keys[i]] !== null) { - typename = uniqueTypeName(format(keyname), globallySeenTypeNames, previousParents) - globallySeenTypeNames.push(typename) - } else { - typename = uniqueTypeName(format(keyname), seenTypeNames) - seenTypeNames.push(typename) - } - - append(typename+" "); - parent = typename - parseScope(scope[keys[i]], depth); - append(' `json:"'+keyname); - if (allOmitempty || (omitempty && omitempty[keys[i]] === true)) - { - append(',omitempty'); - } - if (example && scope[keys[i]] !== "" && typeof scope[keys[i]] !== "object") - { - append('" example:"'+scope[keys[i]]) - } - append('"`\n'); - } - indent(--tabs); - append("}"); - previousParents = oldParents; - } - 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; - } - - // Generate a unique name to avoid duplicate struct field names. - // This function appends a number at the end of the field name. - function uniqueTypeName(name, seen, prefix=null) { - if (seen.indexOf(name) === -1) { - return name; - } - - // check if we can get a unique name by prefixing it - if(prefix) { - name = prefix+name - if (seen.indexOf(name) === -1) { - return name; - } - } - - let i = 0; - while (true) { - let newName = name + i.toString(); - if (seen.indexOf(newName) === -1) { - return newName; - } - - i++; - } - } - - // Sanitizes and formats a string to make an appropriate identifier in Go - function format(str) - { - str = formatNumber(str); - - let sanitized = toProperCase(str).replace(/[^a-z0-9]/ig, "") - if (!sanitized) { - return "NAMING_FAILED"; - } - - // After sanitizing the remaining characters can start with a number. - // Run the sanitized string again trough formatNumber to make sure the identifier is Num[0-9] or Zero_... instead of 1. - return formatNumber(sanitized) - } - - // Adds a prefix to a number to make an appropriate identifier in Go - function formatNumber(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 str; - } - - // Determines the most appropriate Go type - function goType(val) - { - if (val === null) - return "any"; - - switch (typeof val) +// for backwards compatibility +function jsonToGo(json, typename = "AutoGenerated", flatten = true, example = false, allOmitempty = false) { + return jsonToGov2( + json, { - 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 "any"; - } - } - - // 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 "any"; - } - - // Proper cases a string according to Go conventions - function toProperCase(str) - { - // ensure that the SCREAMING_SNAKE_CASE is converted to snake_case - if (str.match(/^[_A-Z0-9]+$/)) { - str = str.toLowerCase(); - } - - // 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 areObjects(objectA, objectB) { - const object = "[object Object]"; - return Object.prototype.toString.call(objectA) === object - && Object.prototype.toString.call(objectB) === object; - } - - function areSameType(objectA, objectB) { - // prototype.toString required to compare Arrays and Objects - const typeA = Object.prototype.toString.call(objectA) - const typeB = Object.prototype.toString.call(objectB) - return typeA === typeB - } - - 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) { - let filename = null - - function jsonToGoWithErrorHandling(json) { - const output = jsonToGo(json) - if (output.error) { - console.error(output.error) - process.exitCode = 1 - } - process.stdout.write(output.go) - } - - process.argv.forEach((val, index) => { - if (index < 2) - return - - if (!val.startsWith('-')) { - filename = val - return - } - - const argument = val.replace(/-/g, '') - if (argument === "big") - console.warn(`Warning: The argument '${argument}' has been deprecated and has no effect anymore`) - else { - console.error(`Unexpected argument ${val} received`) - process.exit(1) - } + typename: typename, + flatten: flatten, + example: example, + allOmitempty: allOmitempty, }) +} - if (filename) { - const fs = require('fs'); - const json = fs.readFileSync(filename, 'utf8'); - jsonToGoWithErrorHandling(json) - return - } +if (typeof module === 'undefined' || !module.parent) { + // being able to run json-to-go backwards compatible + const vm = require('vm'); + const fs = require('fs'); + const jscode = fs.readFileSync('./json-to-go-v2.js'); + const context = { + Buffer, + console, + process, + require, + }; + vm.runInNewContext(jscode, context); +} - if (!filename) { - bufs = [] - process.stdin.on('data', function(buf) { - bufs.push(buf) - }) - process.stdin.on('end', function() { - const json = Buffer.concat(bufs).toString('utf8') - jsonToGoWithErrorHandling(json) - }) - return - } - } else { - module.exports = jsonToGo - } +if (typeof module !== 'undefined') { + module.exports = jsonToGo }