Merge 47888b542d into e1b4f9aafe
				
					
				
			This commit is contained in:
		
						commit
						2373e9ec82
					
				| @ -6,6 +6,7 @@ import { tool as asciiTextDrawer } from './ascii-text-drawer'; | |||||||
| 
 | 
 | ||||||
| import { tool as textToUnicode } from './text-to-unicode'; | import { tool as textToUnicode } from './text-to-unicode'; | ||||||
| import { tool as safelinkDecoder } from './safelink-decoder'; | import { tool as safelinkDecoder } from './safelink-decoder'; | ||||||
|  | import { tool as jsonToGo } from './json-to-go'; | ||||||
| import { tool as pdfSignatureChecker } from './pdf-signature-checker'; | import { tool as pdfSignatureChecker } from './pdf-signature-checker'; | ||||||
| import { tool as numeronymGenerator } from './numeronym-generator'; | import { tool as numeronymGenerator } from './numeronym-generator'; | ||||||
| import { tool as macAddressGenerator } from './mac-address-generator'; | import { tool as macAddressGenerator } from './mac-address-generator'; | ||||||
| @ -107,6 +108,7 @@ export const toolsByCategory: ToolCategory[] = [ | |||||||
|       listConverter, |       listConverter, | ||||||
|       tomlToJson, |       tomlToJson, | ||||||
|       tomlToYaml, |       tomlToYaml, | ||||||
|  |       jsonToGo, | ||||||
|     ], |     ], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								src/tools/json-to-go/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/tools/json-to-go/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | import { Braces } from '@vicons/tabler'; | ||||||
|  | import { defineTool } from '../tool'; | ||||||
|  | 
 | ||||||
|  | export const tool = defineTool({ | ||||||
|  |   name: 'JSON to Go', | ||||||
|  |   path: '/json-to-go', | ||||||
|  |   description: 'Convert JSON to Go struct', | ||||||
|  |   keywords: ['json', 'parse', 'go', 'convert', 'transform'], | ||||||
|  |   component: () => import('./json-to-go.vue'), | ||||||
|  |   icon: Braces, | ||||||
|  |   createdAt: new Date('2024-04-02'), | ||||||
|  | }); | ||||||
							
								
								
									
										20
									
								
								src/tools/json-to-go/json-to-go.service.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/tools/json-to-go/json-to-go.service.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | import { describe, expect, it } from 'vitest'; | ||||||
|  | import { jsonToGo } from './json-to-go.service'; | ||||||
|  | import testCases from './json-to-go.test.data.json'; | ||||||
|  | 
 | ||||||
|  | describe('json-to-go', () => { | ||||||
|  |   describe('jsonToGo', () => { | ||||||
|  |     for (const includeExampleData of [true, false]) { | ||||||
|  |       it(`must return correct results (includeExampleData = ${includeExampleData})`, () => { | ||||||
|  |         for (const testCase of testCases) { | ||||||
|  |           const got = jsonToGo(testCase.input, '', false, includeExampleData); | ||||||
|  |           const expected = includeExampleData | ||||||
|  |             ? testCase.expectedWithExample | ||||||
|  |             : testCase.expected; | ||||||
|  | 
 | ||||||
|  |           expect(got.go).to.equal(expected); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										430
									
								
								src/tools/json-to-go/json-to-go.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										430
									
								
								src/tools/json-to-go/json-to-go.service.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,430 @@ | |||||||
|  | // JSON-to-Go
 | ||||||
|  | // by Matt Holt
 | ||||||
|  | // https://github.com/mholt/json-to-go
 | ||||||
|  | // A simple utility to translate JSON into a Go type definition.
 | ||||||
|  | export function jsonToGo(json: string, typename: string | '', flatten = true, example = false, allOmitempty = false) { | ||||||
|  |   let data; | ||||||
|  |   let scope; | ||||||
|  |   let go = ''; | ||||||
|  |   let tabs = 0; | ||||||
|  |   const seen: { [key: string]: any } = {}; | ||||||
|  |   const stack: any[] = []; | ||||||
|  |   let accumulator = ''; | ||||||
|  |   const innerTabs = 0; | ||||||
|  |   let parent = ''; | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     data = JSON.parse(json.replace(/(:\s*\[?\s*-?\d*)\.0/g, '$1.1')); // NOSONAR : hack that forces floats to stay as floats
 | ||||||
|  |     scope = data; | ||||||
|  |   } | ||||||
|  |   catch (e) { | ||||||
|  |     return { | ||||||
|  |       go: '', | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   typename = format(typename || 'AutoGenerated'); | ||||||
|  |   append(`type ${typename} `); | ||||||
|  | 
 | ||||||
|  |   parseScope(scope); | ||||||
|  | 
 | ||||||
|  |   return { | ||||||
|  |     go: flatten | ||||||
|  |       ? go += accumulator | ||||||
|  |       : go, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   function parseScope(scope: string | any[] | null, 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 = {} as Record<string, { value: string; count: number }>; | ||||||
|  | 
 | ||||||
|  |           // for each field counts how many times appears
 | ||||||
|  |           for (let i = 0; i < scopeLength; i++) { | ||||||
|  |             const keys = Object.keys(scope[i]); | ||||||
|  |             for (const 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); | ||||||
|  |           const struct = {} as Record<string, string>; | ||||||
|  |           const omitempty = {} as Record<string, boolean>; | ||||||
|  |           for (const k in keys) { | ||||||
|  |             const keyname = keys[k]; | ||||||
|  |             const 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 || 'any'); | ||||||
|  |           } | ||||||
|  |           else { | ||||||
|  |             append(sliceType || 'any'); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       else { | ||||||
|  |         if (flatten) { | ||||||
|  |           if (depth >= 2) { | ||||||
|  |             appender(parent); | ||||||
|  |           } | ||||||
|  |           else { | ||||||
|  |             append(parent); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         parseStruct(depth + 1, innerTabs, scope, undefined); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |       if (flatten && depth >= 2) { | ||||||
|  |         appender(goType(scope)); | ||||||
|  |       } | ||||||
|  |       else { | ||||||
|  |         append(goType(scope)); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function parseStruct(depth: number | undefined, innerTabs: number, scope: { [x: string]: any }, omitempty: { [x: string]: boolean } | undefined) { | ||||||
|  |     if (flatten) { | ||||||
|  |       if (depth !== undefined) { | ||||||
|  |         stack.push( | ||||||
|  |           depth >= 2 | ||||||
|  |             ? '\n' | ||||||
|  |             : '', | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const seenTypeNames = []; | ||||||
|  | 
 | ||||||
|  |     if (flatten && depth !== undefined && 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 (const i in keys) { | ||||||
|  |         const keyname = getOriginalName(keys[i]); | ||||||
|  |         indenter(innerTabs); | ||||||
|  |         const 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('}'); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |       append('struct {\n'); | ||||||
|  |       ++tabs; | ||||||
|  |       const keys = Object.keys(scope); | ||||||
|  |       for (const i in keys) { | ||||||
|  |         const keyname = getOriginalName(keys[i]); | ||||||
|  |         indent(tabs); | ||||||
|  |         const 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('}'); | ||||||
|  |     } | ||||||
|  |     if (flatten) { | ||||||
|  |       accumulator += stack.pop(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function indent(tabs: number) { | ||||||
|  |     for (let i = 0; i < tabs; i++) { | ||||||
|  |       go += '\t'; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function append(str: string) { | ||||||
|  |     go += str; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function indenter(tabs: number) { | ||||||
|  |     for (let i = 0; i < tabs; i++) { | ||||||
|  |       stack[stack.length - 1] += '\t'; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function appender(str: string) { | ||||||
|  |     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: string, seen: string | any[]) { | ||||||
|  |     if (!seen.includes(name)) { | ||||||
|  |       return name; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let i = 0; | ||||||
|  |     while (true) { | ||||||
|  |       const newName = name + i.toString(); | ||||||
|  |       if (!seen.includes(newName)) { | ||||||
|  |         return newName; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       i++; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   // Sanitizes and formats a string to make an appropriate identifier in Go
 | ||||||
|  |   function format(str: any) { | ||||||
|  |     str = formatNumber(str); | ||||||
|  | 
 | ||||||
|  |     const 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: string) { | ||||||
|  |     if (!str) { | ||||||
|  |       return ''; | ||||||
|  |     } | ||||||
|  |     else if (str.match(/^\d+$/)) { | ||||||
|  |       str = `Num${str}`; | ||||||
|  |     } | ||||||
|  |     else if (str.charAt(0).match(/\d/)) { | ||||||
|  |       const numbers: { [key: string]: string } = { | ||||||
|  |         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: string | number | null) { | ||||||
|  |     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: string, typ2: string) { | ||||||
|  |     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: string) { | ||||||
|  |     // 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, (unused: any, sep: any, frag: string | string[]) => { | ||||||
|  |       if (!Array.isArray(frag) && commonInitialisms.includes(frag.toUpperCase())) { | ||||||
|  |         return sep + frag.toUpperCase(); | ||||||
|  |       } | ||||||
|  |       else { | ||||||
|  |         return sep + frag[0].toUpperCase() + (frag as string).substr(1).toLowerCase(); | ||||||
|  |       } | ||||||
|  |     }).replace(/([A-Z])([a-z]+)/g, (unused: any, sep: any, frag: string) => { | ||||||
|  |       if (commonInitialisms.includes(sep + frag.toUpperCase())) { | ||||||
|  |         return (sep + frag).toUpperCase(); | ||||||
|  |       } | ||||||
|  |       else { return sep + frag; } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function uuidv4() { | ||||||
|  |     return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { | ||||||
|  |       const r = Math.random() * 16 | 0; // NOSONAR
 | ||||||
|  |       const v = c === 'x' ? r : (r & 0x3 | 0x8); | ||||||
|  |       return v.toString(16); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function getOriginalName(unique: string) { | ||||||
|  |     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: any, objectB: any) { | ||||||
|  |     const object = '[object Object]'; | ||||||
|  |     return Object.prototype.toString.call(objectA) === object && Object.prototype.toString.call(objectB) === object; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function compareObjectKeys(itemAKeys: string | any[], itemBKeys: string | any[]) { | ||||||
|  |     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 (const item of itemAKeys) { | ||||||
|  |       if (!itemBKeys.includes(item)) { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function formatScopeKeys(keys: string[]) { | ||||||
|  |     for (const i in keys) { | ||||||
|  |       keys[i] = format(keys[i]); | ||||||
|  |     } | ||||||
|  |     return keys; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										93
									
								
								src/tools/json-to-go/json-to-go.test.data.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/tools/json-to-go/json-to-go.test.data.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | |||||||
|  | 
 | ||||||
|  | [ | ||||||
|  |  { | ||||||
|  |     "input": "{\"SourceCode\": \"exampleDataHere\"}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tSourceCode string `json:\"SourceCode\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tSourceCode string `json:\"SourceCode\" example:\"exampleDataHere\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"source_code\": \"exampleDataHere\"}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tSourceCode string `json:\"source_code\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tSourceCode string `json:\"source_code\" example:\"exampleDataHere\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"sourceCode\": \"exampleDataHere\"}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tSourceCode string `json:\"sourceCode\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tSourceCode string `json:\"sourceCode\" example:\"exampleDataHere\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"SOURCE_CODE\": \"\"}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tSourceCode string `json:\"SOURCE_CODE\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tSourceCode string `json:\"SOURCE_CODE\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"PublicIP\": \"\"}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tPublicIP string `json:\"PublicIP\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tPublicIP string `json:\"PublicIP\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"public_ip\": \"\"}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tPublicIP string `json:\"public_ip\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tPublicIP string `json:\"public_ip\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"publicIP\": \"\"}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tPublicIP string `json:\"publicIP\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tPublicIP string `json:\"publicIP\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"PUBLIC_IP\": \"\"}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tPublicIP string `json:\"PUBLIC_IP\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tPublicIP string `json:\"PUBLIC_IP\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"+1\": \"Fails\", \"-1\": \"This should not cause duplicate field name\"}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tNum1 string `json:\"+1\"`\n\tNum10 string `json:\"-1\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tNum1 string `json:\"+1\" example:\"Fails\"`\n\tNum10 string `json:\"-1\" example:\"This should not cause duplicate field name\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"age\": 46}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tAge int `json:\"age\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tAge int `json:\"age\" example:\"46\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"negativeFloat\": -1.00}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tNegativeFloat float64 `json:\"negativeFloat\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tNegativeFloat float64 `json:\"negativeFloat\" example:\"-1.1\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"zeroFloat\": 0.00}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tZeroFloat float64 `json:\"zeroFloat\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tZeroFloat float64 `json:\"zeroFloat\" example:\"0.1\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"positiveFloat\": 1.00}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tPositiveFloat float64 `json:\"positiveFloat\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tPositiveFloat float64 `json:\"positiveFloat\" example:\"1.1\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"negativeFloats\": [-1.00, -2.00, -3.00]}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tNegativeFloats []float64 `json:\"negativeFloats\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tNegativeFloats []float64 `json:\"negativeFloats\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"zeroFloats\": [0.00, 0.00, 0.00]}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tZeroFloats []float64 `json:\"zeroFloats\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tZeroFloats []float64 `json:\"zeroFloats\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"positiveFloats\": [1.00, 2.00, 3.00]}", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tPositiveFloats []float64 `json:\"positiveFloats\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tPositiveFloats []float64 `json:\"positiveFloats\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"topLevel\": { \"secondLevel\": \"exampleDataHere\"} }", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tTopLevel struct {\n\t\tSecondLevel string `json:\"secondLevel\"`\n\t} `json:\"topLevel\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tTopLevel struct {\n\t\tSecondLevel string `json:\"secondLevel\" example:\"exampleDataHere\"`\n\t} `json:\"topLevel\"`\n}" | ||||||
|  |  }, | ||||||
|  |  { | ||||||
|  |     "input": "{\"people\": [{ \"name\": \"Frank\"}, {\"name\": \"Dennis\"}, {\"name\": \"Dee\"}, {\"name\": \"Charley\"}, {\"name\":\"Mac\"}] }", | ||||||
|  |     "expected": "type AutoGenerated struct {\n\tPeople []struct {\n\t\tName string `json:\"name\"`\n\t} `json:\"people\"`\n}", | ||||||
|  |     "expectedWithExample": "type AutoGenerated struct {\n\tPeople []struct {\n\t\tName string `json:\"name\" example:\"Frank\"`\n\t} `json:\"people\"`\n}" | ||||||
|  |  } | ||||||
|  | ] | ||||||
							
								
								
									
										48
									
								
								src/tools/json-to-go/json-to-go.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/tools/json-to-go/json-to-go.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | |||||||
|  | <script setup lang="ts"> | ||||||
|  | import JSON5 from 'json5'; | ||||||
|  | import { jsonToGo } from './json-to-go.service'; | ||||||
|  | import type { UseValidationRule } from '@/composable/validation'; | ||||||
|  | import TextareaCopyable from '@/components/TextareaCopyable.vue'; | ||||||
|  | 
 | ||||||
|  | const definitions = ref(true); | ||||||
|  | const omitempty = ref(false); | ||||||
|  | const example = ref(false); | ||||||
|  | const jsonInput = ref(''); | ||||||
|  | const goOutput = computed(() => jsonToGo(jsonInput.value, '', !definitions.value, example.value, omitempty.value).go); | ||||||
|  | const rules: UseValidationRule<string>[] = [ | ||||||
|  |   { | ||||||
|  |     validator: (v: string) => v === '' || JSON5.parse(v), | ||||||
|  |     message: 'Provided JSON is not valid.', | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <c-card title="JSON to Go"> | ||||||
|  |     <n-form-item label="Inline type definitions" label-placement="left"> | ||||||
|  |       <n-switch v-model:value="definitions" /> | ||||||
|  |     </n-form-item> | ||||||
|  |     <n-form-item label="Omit Empty" label-placement="left"> | ||||||
|  |       <n-switch v-model:value="omitempty" /> | ||||||
|  |     </n-form-item> | ||||||
|  |     <n-form-item label="Example" label-placement="left"> | ||||||
|  |       <n-switch v-model:value="example" /> | ||||||
|  |     </n-form-item> | ||||||
|  |     <c-input-text | ||||||
|  |       v-model:value="jsonInput" | ||||||
|  |       multiline | ||||||
|  |       placeholder="Put your json string here..." | ||||||
|  |       rows="20" | ||||||
|  |       label="JSON to GO" | ||||||
|  |       :validation-rules="rules" | ||||||
|  |       raw-text | ||||||
|  |       mb-5 | ||||||
|  |     /> | ||||||
|  |   </c-card> | ||||||
|  |   <c-card title="Your Go String"> | ||||||
|  |     <TextareaCopyable | ||||||
|  |       :value="goOutput" | ||||||
|  |       language="go" | ||||||
|  |     /> | ||||||
|  |   </c-card> | ||||||
|  | </template> | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user