jsonToGo

try to take over the world!

  1. // ==UserScript==
  2. // @name jsonToGo
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description try to take over the world!
  6. // @author You
  7. // @match https://*/*
  8. // @match http://*/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. function jsonToGo(json, typename, flatten = true)
  13. {
  14. let data;
  15. let scope;
  16. let go = "";
  17. let tabs = 0;
  18. let typeNames = []
  19.  
  20. const seen = {};
  21. const stack = [];
  22. let accumulator = "";
  23. let innerTabs = 0;
  24. let parent = "";
  25.  
  26. try
  27. {
  28. data = JSON.parse(json.replace(/:(\s*\d*)\.0/g, ":$1.1")); // hack that forces floats to stay as floats
  29. scope = data;
  30. }
  31. catch (e)
  32. {
  33. return {
  34. go: "",
  35. error: e.message
  36. };
  37. }
  38.  
  39. typename = format(typename || "AutoGenerated");
  40. append(`type ${typename} `);
  41.  
  42. parseScope(scope);
  43. const result = flatten ? go += accumulator : go
  44. console.log(result)
  45. return
  46.  
  47. function parseScope(scope, depth = 0)
  48. {
  49. if (typeof scope === "object" && scope !== null)
  50. {
  51. if (Array.isArray(scope))
  52. {
  53. let sliceType;
  54. const scopeLength = scope.length;
  55.  
  56. for (let i = 0; i < scopeLength; i++)
  57. {
  58. const thisType = goType(scope[i]);
  59. if (!sliceType)
  60. sliceType = thisType;
  61. else if (sliceType != thisType)
  62. {
  63. sliceType = mostSpecificPossibleGoType(thisType, sliceType);
  64. if (sliceType == "interface{}")
  65. break;
  66. }
  67. }
  68.  
  69. const slice = flatten && ["struct", "slice"].includes(sliceType)
  70. ? `[]${parent}`
  71. : `[]`;
  72.  
  73. if (flatten && depth >= 2)
  74. appender(slice);
  75. else
  76. append(slice)
  77. if (sliceType == "struct") {
  78. const allFields = {};
  79.  
  80. // for each field counts how many times appears
  81. for (let i = 0; i < scopeLength; i++)
  82. {
  83. const keys = Object.keys(scope[i])
  84. for (let k in keys)
  85. {
  86. let keyname = keys[k];
  87. if (!(keyname in allFields)) {
  88. allFields[keyname] = {
  89. value: scope[i][keyname],
  90. count: 0
  91. }
  92. }
  93. else {
  94. const existingValue = allFields[keyname].value;
  95. const currentValue = scope[i][keyname];
  96.  
  97. if (compareObjects(existingValue, currentValue)) {
  98. const comparisonResult = compareObjectKeys(
  99. Object.keys(currentValue),
  100. Object.keys(existingValue)
  101. )
  102. if (!comparisonResult) {
  103. keyname = `${keyname}_${uuidv4()}`;
  104. allFields[keyname] = {
  105. value: currentValue,
  106. count: 0
  107. };
  108. }
  109. }
  110. }
  111. allFields[keyname].count++;
  112. }
  113. }
  114.  
  115. // create a common struct with all fields found in the current array
  116. // omitempty dict indicates if a field is optional
  117. const keys = Object.keys(allFields), struct = {}, omitempty = {};
  118. for (let k in keys)
  119. {
  120. const keyname = keys[k], elem = allFields[keyname];
  121.  
  122. struct[keyname] = elem.value;
  123. omitempty[keyname] = elem.count != scopeLength;
  124. }
  125. parseStruct(depth + 1, innerTabs, struct, omitempty); // finally parse the struct !!
  126. }
  127. else if (sliceType == "slice") {
  128. parseScope(scope[0], depth)
  129. }
  130. else {
  131. if (flatten && depth >= 2) {
  132. appender(sliceType || "interface{}");
  133. } else {
  134. append(sliceType || "interface{}");
  135. }
  136. }
  137. }
  138. else
  139. {
  140. if (flatten) {
  141. if (depth >= 2){
  142. appender(parent)
  143. }
  144. else {
  145. append(parent)
  146. }
  147. }
  148. parseStruct(depth + 1, innerTabs, scope);
  149. }
  150. }
  151. else {
  152. if (flatten && depth >= 2){
  153. appender(goType(scope));
  154. }
  155. else {
  156. append(goType(scope));
  157. }
  158. }
  159. }
  160.  
  161. function parseStruct(depth, innerTabs, scope, omitempty)
  162. {
  163. if (flatten) {
  164. stack.push(
  165. depth >= 2
  166. ? "\n"
  167. : ""
  168. )
  169. }
  170.  
  171. if (flatten && depth >= 2)
  172. {
  173. const parentType = `type ${parent}`;
  174. const scopeKeys = formatScopeKeys(Object.keys(scope));
  175.  
  176. // this can only handle two duplicate items
  177. // future improvement will handle the case where there could
  178. // three or more duplicate keys with different values
  179. if (parent in seen && compareObjectKeys(scopeKeys, seen[parent])) {
  180. stack.pop();
  181. return
  182. }
  183. seen[parent] = scopeKeys;
  184.  
  185. appender(`${parentType} struct {\n`);
  186. ++innerTabs;
  187. const keys = Object.keys(scope);
  188. for (let i in keys)
  189. {
  190. const keyname = getOriginalName(keys[i]);
  191. indenter(innerTabs)
  192. const typename = format(keyname)
  193. appender(typename+" ");
  194. parent = typename
  195. parseScope(scope[keys[i]], depth);
  196. appender(' `json:"'+keyname);
  197. if (omitempty && omitempty[keys[i]] === true)
  198. {
  199. appender(',omitempty');
  200. }
  201. appender('"`\n');
  202. }
  203. indenter(--innerTabs);
  204. appender("}");
  205. }
  206. else
  207. {
  208. append("struct {\n");
  209. ++tabs;
  210. const keys = Object.keys(scope);
  211. for (let i in keys)
  212. {
  213. const keyname = getOriginalName(keys[i]);
  214. indent(tabs);
  215. const typename = format(keyname);
  216. append(typename+" ");
  217. parent = typename
  218. parseScope(scope[keys[i]], depth);
  219. append(' `json:"'+keyname);
  220. if (omitempty && omitempty[keys[i]] === true)
  221. {
  222. append(',omitempty');
  223. }
  224. append('"`\n');
  225. }
  226. indent(--tabs);
  227. append("}");
  228. }
  229. if (flatten)
  230. accumulator += stack.pop();
  231. }
  232.  
  233. function indent(tabs)
  234. {
  235. for (let i = 0; i < tabs; i++)
  236. go += '\t';
  237. }
  238.  
  239. function append(str)
  240. {
  241. go += str;
  242. }
  243.  
  244. function indenter(tabs)
  245. {
  246. for (let i = 0; i < tabs; i++)
  247. stack[stack.length - 1] += '\t';
  248. }
  249.  
  250. function appender(str)
  251. {
  252. stack[stack.length - 1] += str;
  253. }
  254.  
  255. // Sanitizes and formats a string to make an appropriate identifier in Go
  256. function format(str)
  257. {
  258. if (!str)
  259. return "";
  260. else if (str.match(/^\d+$/))
  261. str = "Num" + str;
  262. else if (str.charAt(0).match(/\d/))
  263. {
  264. const numbers = {'0': "Zero_", '1': "One_", '2': "Two_", '3': "Three_",
  265. '4': "Four_", '5': "Five_", '6': "Six_", '7': "Seven_",
  266. '8': "Eight_", '9': "Nine_"};
  267. str = numbers[str.charAt(0)] + str.substr(1);
  268. }
  269. return toProperCase(str).replace(/[^a-z0-9]/ig, "") || "NAMING_FAILED";
  270. }
  271.  
  272. // Determines the most appropriate Go type
  273. function goType(val)
  274. {
  275. if (val === null)
  276. return "interface{}";
  277.  
  278. switch (typeof val)
  279. {
  280. case "string":
  281. if (/\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(\+\d\d:\d\d|Z)/.test(val))
  282. return "time.Time";
  283. else
  284. return "string";
  285. case "number":
  286. if (val % 1 === 0)
  287. {
  288. if (val > -2147483648 && val < 2147483647)
  289. return "int";
  290. else
  291. return "int64";
  292. }
  293. else
  294. return "float64";
  295. case "boolean":
  296. return "bool";
  297. case "object":
  298. if (Array.isArray(val))
  299. return "slice";
  300. return "struct";
  301. default:
  302. return "interface{}";
  303. }
  304. }
  305.  
  306. // Given two types, returns the more specific of the two
  307. function mostSpecificPossibleGoType(typ1, typ2)
  308. {
  309. if (typ1.substr(0, 5) == "float"
  310. && typ2.substr(0, 3) == "int")
  311. return typ1;
  312. else if (typ1.substr(0, 3) == "int"
  313. && typ2.substr(0, 5) == "float")
  314. return typ2;
  315. else
  316. return "interface{}";
  317. }
  318.  
  319. // Proper cases a string according to Go conventions
  320. function toProperCase(str)
  321. {
  322. // https://github.com/golang/lint/blob/5614ed5bae6fb75893070bdc0996a68765fdd275/lint.go#L771-L810
  323. const commonInitialisms = [
  324. "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP",
  325. "HTTPS", "ID", "IP", "JSON", "LHS", "QPS", "RAM", "RHS", "RPC", "SLA",
  326. "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "UID", "UUID",
  327. "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS"
  328. ];
  329.  
  330. return str.replace(/(^|[^a-zA-Z])([a-z]+)/g, function(unused, sep, frag)
  331. {
  332. if (commonInitialisms.indexOf(frag.toUpperCase()) >= 0)
  333. return sep + frag.toUpperCase();
  334. else
  335. return sep + frag[0].toUpperCase() + frag.substr(1).toLowerCase();
  336. }).replace(/([A-Z])([a-z]+)/g, function(unused, sep, frag)
  337. {
  338. if (commonInitialisms.indexOf(sep + frag.toUpperCase()) >= 0)
  339. return (sep + frag).toUpperCase();
  340. else
  341. return sep + frag;
  342. });
  343. }
  344.  
  345. function uuidv4() {
  346. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  347. var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
  348. return v.toString(16);
  349. });
  350. }
  351.  
  352. function getOriginalName(unique) {
  353. 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
  354. const uuidLength = 36;
  355.  
  356. if (unique.length >= uuidLength) {
  357. const tail = unique.substr(-uuidLength);
  358. if (reLiteralUUID.test(tail)) {
  359. return unique.slice(0, -1 * (uuidLength + 1))
  360. }
  361. }
  362. return unique
  363. }
  364.  
  365. function compareObjects(objectA, objectB) {
  366. const object = "[object Object]";
  367. return Object.prototype.toString.call(objectA) === object
  368. && Object.prototype.toString.call(objectB) === object;
  369. }
  370.  
  371. function compareObjectKeys(itemAKeys, itemBKeys) {
  372. const lengthA = itemAKeys.length;
  373. const lengthB = itemBKeys.length;
  374.  
  375. // nothing to compare, probably identical
  376. if (lengthA == 0 && lengthB == 0)
  377. return true;
  378.  
  379. // duh
  380. if (lengthA != lengthB)
  381. return false;
  382.  
  383. for (let item of itemAKeys) {
  384. if (!itemBKeys.includes(item))
  385. return false;
  386. }
  387. return true;
  388. }
  389.  
  390. function formatScopeKeys(keys) {
  391. for (let i in keys) {
  392. keys[i] = format(keys[i]);
  393. }
  394. return keys
  395. }
  396. }
  397.  
  398. if (typeof module != 'undefined') {
  399. if (!module.parent) {
  400. if (process.argv.length > 2 && process.argv[2] === '-big') {
  401. bufs = []
  402. process.stdin.on('data', function(buf) {
  403. bufs.push(buf)
  404. })
  405. process.stdin.on('end', function() {
  406. const json = Buffer.concat(bufs).toString('utf8')
  407. console.log(jsonToGo(json).go)
  408. })
  409. } else {
  410. process.stdin.on('data', function(buf) {
  411. const json = buf.toString('utf8')
  412. console.log(jsonToGo(json).go)
  413. })
  414. }
  415. } else {
  416. module.exports = jsonToGo
  417. }
  418. }
  419.  
  420. function jsonToTs (data) {
  421. if (typeof data === 'object') {
  422. if (typeof data.length !== 'undefined') {
  423. if (data.length > 0) {
  424. return `Array<${jsonToTs(data[0])}>`
  425. } else {
  426. return 'Array<any>'
  427. }
  428. }
  429. const keys = Object.keys(data)
  430. if (keys.length === 0) {
  431. return 'object'
  432. }
  433. let a = Object.keys(data).map(v => {
  434. const type = jsonToTs(data[v])
  435. return `${v}: ${type}`
  436. }).join(',\n')
  437. a = `{\n${a}\n}`
  438. return a
  439. } else {
  440. return typeof data
  441. }
  442. }
  443.  
  444. (function() {
  445. 'use strict';
  446. document.jsonToGo = jsonToGo
  447. document.jsonToTs = (data) => {
  448. return jsonToTs(JSON.parse(data))
  449. }
  450. })();