diff --git a/json-to-go.js b/json-to-go.js index 870ce0b..b9a4fa2 100644 --- a/json-to-go.js +++ b/json-to-go.js @@ -52,7 +52,7 @@ function jsonToGo(json, typename, flatten = true, example = false, allOmitempty }; - function parseScope(scope, depth = 0) + function parseScope(scope, depth = 0, forceOmitEmptyNonArrays = false) { if (typeof scope === "object" && scope !== null) { @@ -82,8 +82,11 @@ function jsonToGo(json, typename, flatten = true, example = false, allOmitempty appender(slice); else append(slice) + + // TODO: structs need a proper recursive solution to make merging generic if (sliceType == "struct") { const allFields = {}; + let insideOmitEmpty = false; // for each field counts how many times appears for (let i = 0; i < scopeLength; i++) @@ -112,6 +115,55 @@ function jsonToGo(json, typename, flatten = true, example = false, allOmitempty } if (areObjects(existingValue, currentValue)) { + const currentKeys = Object.keys(currentValue) + const existingKeys = Object.keys(existingValue) + + // try to merge two object fields instead of creating separate ones + // TODO: find a proper handling of omitempty for nested structs instead of + // fake-forcing omitempty on all nested elements + if (existingKeys.length > 0 && currentKeys.length > 0) { + if (!allOmitempty) { + // check if any of the existingKeys (which are assumed to be mandatory), + // is not in the currentKeys + for (const key of existingKeys) { + if (!Object.keys(currentKeys).includes(key)) { + insideOmitEmpty = true // TODO: only set this one key to omitempty + break + } + } + } + + var mergedValues = existingValue + for (const key of currentKeys) { + // check if key has been found previously + if (!Object.keys(mergedValues).includes(key)) { + mergedValues[key] = currentValue[key] + insideOmitEmpty = true // TODO: only set this one key to omitempty + continue + } + + // check if types between previously found values and the current value + if (!areSameType(mergedValues[key], currentValue[key])) { + if (mergedValues[key] !== null) { + mergedValues[key] = null // force type "any" if types are not identical + console.warn(`Warning: nested key "${key}" uses multiple types. Defaulting to type "any".`) + } + } + } + + allFields[keyname].value = mergedValues; + allFields[keyname].count++; + continue; + } + + // if both one of the two objects is empty assume they are identical + if (currentKeys.length == 0 && existingKeys.length > 0 || + currentKeys.length > 0 && existingKeys.length == 0) { + allFields[keyname].count++; + insideOmitEmpty = true // as the whole object is empty, all nested elements are omitempty + continue; + } + const comparisonResult = compareObjectKeys( Object.keys(currentValue), Object.keys(existingValue) @@ -139,7 +191,7 @@ function jsonToGo(json, typename, flatten = true, example = false, allOmitempty struct[keyname] = elem.value; omitempty[keyname] = elem.count != scopeLength; } - parseStruct(depth + 1, innerTabs, struct, omitempty, previousParents); // finally parse the struct !! + parseStruct(depth + 1, innerTabs, struct, omitempty, previousParents, insideOmitEmpty); // finally parse the struct !! } else if (sliceType == "slice") { parseScope(scope[0], depth) @@ -162,7 +214,16 @@ function jsonToGo(json, typename, flatten = true, example = false, allOmitempty append(parent) } } - parseStruct(depth + 1, innerTabs, scope, false, previousParents); + + const omitempty = {}; + if (forceOmitEmptyNonArrays) { + const allKeys = Object.keys(scope) + for (let k in allKeys) { + const keyname = allKeys[k]; + omitempty[keyname] = true; + } + } + parseStruct(depth + 1, innerTabs, scope, omitempty, previousParents); } } else { @@ -175,7 +236,7 @@ function jsonToGo(json, typename, flatten = true, example = false, allOmitempty } } - function parseStruct(depth, innerTabs, scope, omitempty, oldParents) + function parseStruct(depth, innerTabs, scope, omitempty, oldParents, insideOmitEmpty) { if (flatten) { stack.push( @@ -221,7 +282,7 @@ function jsonToGo(json, typename, flatten = true, example = false, allOmitempty appender(typename+" "); parent = typename - parseScope(scope[keys[i]], depth); + parseScope(scope[keys[i]], depth, insideOmitEmpty); appender(' `json:"'+keyname); if (allOmitempty || (omitempty && omitempty[keys[i]] === true)) { diff --git a/json-to-go.test.js b/json-to-go.test.js index c2a6e3f..56700dc 100644 --- a/json-to-go.test.js +++ b/json-to-go.test.js @@ -163,6 +163,8 @@ function testFiles() { "duplicate-top-level-structs", "double-nested-objects", "array-with-nonmatching-types", + "array-with-mergable-objects", + "array-with-mergable-empty-object", ]; for (const testCase of testCases) { diff --git a/tests/array-with-mergable-empty-object.go b/tests/array-with-mergable-empty-object.go new file mode 100644 index 0000000..f63b01e --- /dev/null +++ b/tests/array-with-mergable-empty-object.go @@ -0,0 +1,14 @@ +type AutoGenerated struct { + Booleanfield bool `json:"booleanfield"` + Somearray []Somearray `json:"somearray"` + Date string `json:"date"` +} +type Features struct { + Age int `json:"age,omitempty"` + Height int `json:"height,omitempty"` +} +type Somearray struct { + ID int `json:"id"` + Name string `json:"name"` + Features Features `json:"features"` +} diff --git a/tests/array-with-mergable-empty-object.json b/tests/array-with-mergable-empty-object.json new file mode 100644 index 0000000..2b895d6 --- /dev/null +++ b/tests/array-with-mergable-empty-object.json @@ -0,0 +1,19 @@ +{ + "booleanfield": true, + "somearray": [ + { + "id": 1, + "name": "John Doe", + "features": { + "age": 49, + "height": 175 + } + }, + { + "id": 3, + "name": "John Doe", + "features": {} + } + ], + "date": "2024-07-24" +} diff --git a/tests/array-with-mergable-objects.go b/tests/array-with-mergable-objects.go new file mode 100644 index 0000000..52859c6 --- /dev/null +++ b/tests/array-with-mergable-objects.go @@ -0,0 +1,15 @@ +type AutoGenerated struct { + Booleanfield bool `json:"booleanfield"` + Inconsistentarray []Inconsistentarray `json:"inconsistentarray"` + Date string `json:"date"` +} +type Features struct { + Age int `json:"age,omitempty"` + Height any `json:"height,omitempty"` + Gender string `json:"gender,omitempty"` +} +type Inconsistentarray struct { + ID int `json:"id"` + Name string `json:"name"` + Features Features `json:"features"` +} diff --git a/tests/array-with-mergable-objects.json b/tests/array-with-mergable-objects.json new file mode 100644 index 0000000..1b68d74 --- /dev/null +++ b/tests/array-with-mergable-objects.json @@ -0,0 +1,29 @@ +{ + "booleanfield": true, + "inconsistentarray": [ + { + "id": 1, + "name": "John Doe", + "features": { + "age": 49, + "height": 175 + } + }, + { + "id": 2, + "name": "Jane Doe", + "features": { + "height": 164 + } + }, + { + "id": 3, + "name": "John Doe", + "features": { + "gender": "male", + "height": "unknown" + } + } + ], + "date": "2024-07-22" +}