xmlbuilder2
xmlbuilder2 copied to clipboard
Maintain ordering
Is your feature request related to a problem? Please describe.
In order to do xml -> json -> xml
without any re-ordering, children must always be in an array, not grouped by their tag type.
<foo>
<bar></bar>
<baz></baz>
<bar></bar>
</foo>
Describe the solution you'd like
Like xml-js
does with {compact: false}
. An option where all children are always an array.
Describe alternatives you've considered
Manipulating the DOM instead which maintains ordering.
Did you try the verbose
option?
https://oozcitak.github.io/xmlbuilder2/serialization.html#js-object-and-map-serializers
Yeh, but if you have a node that has children of different element types interspersed, it groups by the element type. Which loses ordering.
Can you create a test case to reproduce?
The structure of the object seems to change depending on whether there are intersperesed children of different types or not. In order to use the serialized object, I need a consistent structure.
<?xml version="1.0"?>
<root>
<person age="35"/>
<person age="30"/>
<ele>simple element</ele>
</root>
{
"root": [
{
"person": [
{
"@age": "35"
},
{
"@age": "30"
}
],
"ele": [
"simple element"
]
}
]
}
<?xml version="1.0"?>
<root>
<person age="35"/>
<ele>simple element</ele>
<person age="30"/>
</root>
{
"root": [
{
"#": [
{
"person": [
{
"@age": "35"
}
]
},
{
"ele": [
"simple element"
]
},
{
"person": [
{
"@age": "30"
}
]
}
]
}
]
}
Test case:
// @ts-nocheck
import $$ from '../TestHelpers'
describe('Replicate issue', () => {
test('#104 - Maintain ordering for interspersed children of different element type', () => {
const xmlA = $$.t`
<?xml version="1.0"?>
<root>
<person age="35"/>
<ele>simple element</ele>
<person age="30"/>
</root>
`
const xmlB = $$.t`
<?xml version="1.0"?>
<root>
<person age="35"/>
<person age="30"/>
<ele>simple element</ele>
</root>
`
const expectedObjA = {
'root': {
'#': [
{
'person': {
'@age': '35'
}
},
{
'ele': 'simple element'
},
{
'person': {
'@age': '30'
}
}
]
}
}
const expectedObjB = {
'root': {
'#': [
{
'person': {
'@age': '35'
}
},
{
'person': {
'@age': '30'
}
},
{
'ele': 'simple element'
},
]
}
}
const verbose = true
const group = false
const format = 'map'
const docA = $$.create(xmlA)
const docB = $$.create(xmlB)
const objA = docA.end({format, verbose, group})
const objB = docB.end({format, verbose, group})
console.log(JSON.stringify(toObject(objA), null, 2))
console.log(JSON.stringify(toObject(objB), null, 2))
expect(toObject(objA)).toEqual(expectedObjA)
expect(toObject(objB)).toEqual(expectedObjB)
const xmlFromObjA = $$.create(objA).end({prettyPrint: true})
const xmlFromObjB = $$.create(objB).end({prettyPrint: true})
expect(xmlFromObjA).toEqual(xmlA)
expect(xmlFromObjB).toEqual(xmlB)
})
})
const toObject = (map = new Map) => {
if (!(map instanceof Map)) return map
return Object.fromEntries(Array.from(map.entries(), ([k, v]) => {
if (v instanceof Array) {
return [k, v.map(toObject)]
} else if (v instanceof Map) {
return [k, toObject(v)]
} else {
return [k, v]
}
}))
}
So actually, it doesn't lose ordering...but the serialized object is inconsistent, making it difficult to traverse.
~I think format=map
actually works as expected.~
I need to work with a JSON object because I want to modify things, and then diff, which is more difficult with the DOM.
I think verbose
should force all children under a #
- so that regardless of interspersal of children, you get the same object structure. Or could make it a separate option.
@oozcitak Hello! Any update on this? We can't trust that object keys are going to remain in the same order, children need to be contained inside arrays.