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 safelinkDecoder } from './safelink-decoder'; | ||||
| import { tool as jsonToGo } from './json-to-go'; | ||||
| import { tool as pdfSignatureChecker } from './pdf-signature-checker'; | ||||
| import { tool as numeronymGenerator } from './numeronym-generator'; | ||||
| import { tool as macAddressGenerator } from './mac-address-generator'; | ||||
| @ -107,6 +108,7 @@ export const toolsByCategory: ToolCategory[] = [ | ||||
|       listConverter, | ||||
|       tomlToJson, | ||||
|       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