Skip to content

Commit a3f1f63

Browse files
authored
Handle field names with non alphanumeric characters (#99)
When supplying a payload of {"-1":"Fails"} the fieldname will be 1. This will fail when running gofmt(and is not a valid go identifier). This change checks if the sanitized field name is a number. If so it will preprend Num. To avoid duplicate field names because of sanitizing values the code now keeps a map with used fields for the current struct.
1 parent af002ea commit a3f1f63

File tree

2 files changed

+50
-7
lines changed

2 files changed

+50
-7
lines changed

json-to-go.js

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ function jsonToGo(json, typename, flatten = true)
169169
)
170170
}
171171

172+
const seenTypeNames = [];
173+
172174
if (flatten && depth >= 2)
173175
{
174176
const parentType = `type ${parent}`;
@@ -190,7 +192,9 @@ function jsonToGo(json, typename, flatten = true)
190192
{
191193
const keyname = getOriginalName(keys[i]);
192194
indenter(innerTabs)
193-
const typename = format(keyname)
195+
const typename = uniqueTypeName(format(keyname), seenTypeNames)
196+
seenTypeNames.push(typename)
197+
194198
appender(typename+" ");
195199
parent = typename
196200
parseScope(scope[keys[i]], depth);
@@ -213,7 +217,8 @@ function jsonToGo(json, typename, flatten = true)
213217
{
214218
const keyname = getOriginalName(keys[i]);
215219
indent(tabs);
216-
const typename = format(keyname);
220+
const typename = uniqueTypeName(format(keyname), seenTypeNames)
221+
seenTypeNames.push(typename)
217222
append(typename+" ");
218223
parent = typename
219224
parseScope(scope[keys[i]], depth);
@@ -253,9 +258,41 @@ function jsonToGo(json, typename, flatten = true)
253258
stack[stack.length - 1] += str;
254259
}
255260

261+
// Generate a unique name to avoid duplicate struct field names.
262+
// This function appends a number at the end of the field name.
263+
function uniqueTypeName(name, seen) {
264+
if (seen.indexOf(name) === -1) {
265+
return name;
266+
}
267+
268+
let i = 0;
269+
while (true) {
270+
let newName = name + i.toString();
271+
if (seen.indexOf(newName) === -1) {
272+
return newName;
273+
}
274+
275+
i++;
276+
}
277+
}
278+
256279
// Sanitizes and formats a string to make an appropriate identifier in Go
257280
function format(str)
258281
{
282+
str = formatNumber(str);
283+
284+
let sanitized = toProperCase(str).replace(/[^a-z0-9]/ig, "")
285+
if (!sanitized) {
286+
return "NAMING_FAILED";
287+
}
288+
289+
// After sanitizing the remaining characters can start with a number.
290+
// Run the sanitized string again trough formatNumber to make sure the identifier is Num[0-9] or Zero_... instead of 1.
291+
return formatNumber(sanitized)
292+
}
293+
294+
// Adds a prefix to a number to make an appropriate identifier in Go
295+
function formatNumber(str) {
259296
if (!str)
260297
return "";
261298
else if (str.match(/^\d+$/))
@@ -267,7 +304,8 @@ function jsonToGo(json, typename, flatten = true)
267304
'8': "Eight_", '9': "Nine_"};
268305
str = numbers[str.charAt(0)] + str.substr(1);
269306
}
270-
return toProperCase(str).replace(/[^a-z0-9]/ig, "") || "NAMING_FAILED";
307+
308+
return str;
271309
}
272310

273311
// Determines the most appropriate Go type
@@ -324,12 +362,12 @@ function jsonToGo(json, typename, flatten = true)
324362
if (str.match(/^[_A-Z0-9]+$/)) {
325363
str = str.toLowerCase();
326364
}
327-
365+
328366
// https://github.com/golang/lint/blob/5614ed5bae6fb75893070bdc0996a68765fdd275/lint.go#L771-L810
329367
const commonInitialisms = [
330-
"ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP",
331-
"HTTPS", "ID", "IP", "JSON", "LHS", "QPS", "RAM", "RHS", "RPC", "SLA",
332-
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "UID", "UUID",
368+
"ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP",
369+
"HTTPS", "ID", "IP", "JSON", "LHS", "QPS", "RAM", "RHS", "RPC", "SLA",
370+
"SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "UID", "UUID",
333371
"URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS"
334372
];
335373

json-to-go.test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ function test() {
4848
input: '{"PUBLIC_IP": ""}',
4949
expected:
5050
'type AutoGenerated struct {\n\tPublicIP string `json:"PUBLIC_IP"`\n}'
51+
},
52+
{
53+
input: '{"+1": "Fails", "-1": "This should not cause duplicate field name"}',
54+
expected:
55+
'type AutoGenerated struct {\n\tNum1 string `json:"+1"`\n\tNum10 string `json:"-1"`\n}'
5156
}
5257
];
5358

0 commit comments

Comments
 (0)