-
Notifications
You must be signed in to change notification settings - Fork 3
XMLNode.toObject
XMLNode.toObject
is a tool for transforming XMLNodes to plain, JSON.stringify ready javaScript Objects.
It's somewhat similar to XMLNode's detach method, but, in contrast, merges attributes with child nodes and may alter the original document node order, which is convenient for many data processing scenarios, but is unacceptable for general document transformation tasks.
Technically, this is a function generator: a function that, given a bag of options, returns a function (to be used as XMLReader's map
option).
Though exposed as XMLNode's global method for code readability reasons, it's a separate module that may be require
d directly by his historical name MoxyLikeJsonEncoder
reflecting the fact that the basic functionality is modelled after MOXy's one.
const {XMLReader, XMLNode} = require ('xml-toolkit')
const sax = new XMLReader ({
stripSpace: true,
collect: e => true,
filterElements: e => e.localName === 'MessagePrimaryContent',
map: XMLNode.toObject ({
// wrap: true,
// getName: (localName, namespaceURI) => localName.toLowerCase ()
// map: e => ({id: e.Id, label: e.Name}),
})
})
Name | Default | Description |
---|---|---|
wrap | false | If true , top object with element name as key is generated |
getName | (localName, namespaceURI) => localName | XML name to property name transforming function |
map | If set, is applied to the resulting object (think Array.map) |
In short, any xml element
<parent attr1="val1" attr2="val2">
<singleChild>200</singleChild>
<multiChild k="v"/>
<multiChild>OK</multiChild>
<multiChild />
</parent>
is transformed to a js Object
{parent: // omitted with wrap: false option
{
attr1: "val1",
attr2: "val2",
singleChild: "200",
multiChild: [{k: "v", "OK", null}]
}
}
Elements not having any parents are represented by js objects with single key-value pair, where the key is the element name and the value is the js object representing its content:
<MyElement <!--...content...--> </MyElement> -> {MyElement: /* ...content... */}
But, unless the wrap
option is explicitly set to true
, the top object is not present in the output, so only the content object goes downstream:
<MyElement <!--...content...--> </MyElement> -> /* ...content... */
This is the default behavior because, in most cases, the top element name is a known costant, so copying it have no practical sense.
Element attributes are translated into the key-value pairs in his content object:
<MyElement id="1" /> -> {MyElement: {id: "1"}} // wrap: true
<MyElement id="1" /> -> {id: "1"} // default
Note that the value is string, not a number. More on this in the Scalars section.
Child text node of an element is translated to a string representing its content:
<MyElement>1970-01-01</MyElement> -> {MyElement: "1970-01-01"} // wrap: true
<MyElement>1970-01-01</MyElement> -> "1970-01-01" // default
XML elements with both text content and attibutes/child elements cannot be processed properly.
Each child element is treated the same way as the top one, except for it is always added as key-value pair in the content object representing its parent:
<MyElement><id>1</id></MyElement> -> {MyElement: {id: "1"}} // wrap: true
<MyElement><id>1</id></MyElement> -> {id: "1"} // default
So, for instance, child elements with text content give out same results as attributes.
Sequence of nodes with the same name and same parent are translated into one key-value pair, where the key is their name and the value is the array of their content representations:
<MyElement> <id>1</id> <id>2</id> </MyElement> -> {MyElement: {id: ["1", "2"]}} // wrap: true
<MyElement> <id>1</id> <id>2</id> </MyElement> -> {id: ["1", "2"]} // default
Arrays are generated only for multiple homonymous children. There is no way to force the transformer to expose some single valued field as an array.
All leaf scalar values are:
- either
null
; - either
String
s starting and ending with non-blank characters.
No attempt is made to parse text content as Number
, Date
, Boolean
etc.
Each string is trimmed down and, if empty, replaced by null
.
Zero length strings are converted to null
values (as in Oracle Database). For attributes, this is done by AttributesMap.set:
<MyElement id="" /> -> {MyElement: {id: null}} // wrap: true
<MyElement id="" /> -> {id: null}} // default
For an element without a single child node (attribute, text or nested element), the content is null
:
<MyElement></MyElement>, or <MyElement/> -> {MyElement: null} // wrap: true
<MyElement></MyElement>, or <MyElement/> -> null // default
By default, local names are used for both elements and attributes, namespaces are ignored (in this section, we assume wrap
option to be set on):
<x:MyElement x:ID="1" xmlns:x="uri://..." /> ->
{
MyElement: {
ID: "1"
}
}
This behaviour can be altered with the getName
option. For example:
...
getName: (localName, namespaceURI) => '{' + namespaceURI + '}') + localName.toLowerCase (),
...
<x:MyElement x:ID="1" xmlns:x="uri://..." /> ->
{
"{uri://...}myelement": {
"{uri://...}id": "1"
}
}
The map
option makes it possible to apply some transfortation to each resulting object. Consider an example (this time, wrap
is off):
<MyElement id="1" label="Sample" /> -> {id: "1", label: "Sample"}
Having set
map: o => ({...o, is_deleted: false}),
we'll obtain
<MyElement id="1" label="Sample" /> -> {id: "1", label: "Sample", is_deleted: false}