Skip to content

Commit 5f9279f

Browse files
authored
Merge pull request #8 from rmraya/JSON
Implemented JSON <-> XML conversion
2 parents 410b3da + 1a77959 commit 5f9279f

19 files changed

Lines changed: 2833 additions & 17 deletions

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ TypesXML is a native TypeScript XML library and processing toolkit — there are
1717
- Canonical XML renderer compatible with the W3C XML Test Suite rules.
1818
- Strict character validation for XML 1.0/1.1 and optional DTD-validating mode.
1919
- Pure TypeScript implementation with type definitions included—ideal for bundlers and ESM/CJS projects.
20+
- XML↔JSON conversion APIs with both lightweight and lossless modes for simple payloads or fully faithful round-trips.
2021

2122
## SAX Parser
2223

@@ -83,6 +84,7 @@ parser.setValidating(true); // Turns on DTD validation only.
8384
## Documentation & Samples
8485

8586
- Read the step-by-step [TypesXML tutorial](docs/tutorial.md) for guided workflows.
87+
- Use the [JSON and XML Conversion Guide](docs/jsonTutorial.md) to translate between XML documents and JSON objects, with guidance on when to enable the metadata-preserving round-trip mode.
8688
- Explore the runnable examples under [`samples/`](samples/README.md) to see the code in action.
8789

8890
## W3C XML Test Suite

docs/jsonTutorial.md

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
# JSON and XML Conversion Guide
2+
3+
This tutorial covers the TypesXML conversion functions that move between XML documents and JSON objects. Each section shows a focused task so you can apply the API immediately. The conversion functions support two modes: “simple” (default) returns only element content, while “roundtrip” retains metadata such as declarations and DOCTYPE nodes. The XML→JSON section describes both modes with examples. For parser setup, DOM traversal, and SAX handling basics, read the companion [TypesXML Tutorial](tutorial.md).
4+
5+
## Function overview
6+
7+
- `jsonObjectToXmlDocument(data, rootName?)`: Create an `XMLDocument` from JSON. When you omit `rootName`, the converter reuses the single top-level property name from the JSON value (if present); otherwise it falls back to `<json>`.
8+
- `xmlStringToJsonObject(xml, options?)`: Convert an XML string to JSON. Set `options.mode` to `"roundtrip"` when you need the metadata described in the XML→JSON section.
9+
- `xmlFileToJsonObject(path, options?)` and `xmlStreamToJsonObject(stream, options?)`: Stream or load XML and receive JSON. Options mirror the string variant.
10+
- `jsonObjectToXmlFile(data, target, rootName?)`, `jsonFileToXmlDocument`, and `jsonStreamToXmlDocument`: Write or parse XML using the same JSON shape.
11+
12+
Use the sections below to see these functions in action and understand the JSON structure they read and produce.
13+
14+
## JSON → XML
15+
16+
The JSON conversion functions accept plain objects, arrays, and primitive values. Arrays become repeated child elements and objects become nested elements. Ordinary payloads work without extra properties. Use the optional helper keys (`_attributes`, `_text`, and the others listed later) only when you need to represent XML constructs such as attributes, CDATA blocks, comments, or processing instructions. The optional second argument to `jsonObjectToXmlDocument` supplies the root element name. Omit it to use `<json>…</json>`, or rely on the automatic name picked from a single top-level property. When you pass the structure returned by the XML→JSON conversion (documented later as `XmlJsonDocument`), the converter ignores the second argument and reuses the stored `rootName`.
17+
18+
```ts
19+
import { XMLDocument, jsonObjectToXmlDocument } from "typesxml";
20+
21+
function main(): void {
22+
const data: any = {
23+
library: "painters",
24+
books: ["DaVinci", "VanGogh", "Rubens"],
25+
prices: [13000, 5000, 20000]
26+
};
27+
28+
const document: XMLDocument = jsonObjectToXmlDocument(data, "libraryCatalog");
29+
console.log(document.toString());
30+
}
31+
32+
main();
33+
```
34+
35+
Output:
36+
37+
```xml
38+
<libraryCatalog>
39+
<library>painters</library>
40+
<books>
41+
<book>DaVinci</book>
42+
<book>VanGogh</book>
43+
<book>Rubens</book>
44+
</books>
45+
<prices>
46+
<price>13000</price>
47+
<price>5000</price>
48+
<price>20000</price>
49+
</prices>
50+
</libraryCatalog>
51+
```
52+
53+
### Attributes, comments, and CDATA
54+
55+
Use reserved keys to add extra XML constructs:
56+
57+
- `_attributes`: record of attribute names and values.
58+
- `_text`: literal text node content.
59+
- `_cdata`: string or array of strings wrapped in CDATA blocks.
60+
- `_comments`: comment text (string or array of strings).
61+
- `_processingInstructions`: array of `{ target, data? }` entries.
62+
63+
```ts
64+
const product: any = {
65+
_attributes: { sku: "ABC-01" },
66+
name: "Wireless Headphones",
67+
description: {
68+
_cdata: "Crystal clear sound & noise cancellation"
69+
},
70+
notes: {
71+
_comments: "Internal documentation only",
72+
_processingInstructions: [{ target: "style", data: "bold" }],
73+
_text: "Read the manual before use"
74+
}
75+
};
76+
77+
console.log(jsonObjectToXmlDocument(product, "product").toString());
78+
```
79+
80+
```xml
81+
<product sku="ABC-01">
82+
<name>Wireless Headphones</name>
83+
<description><![CDATA[Crystal clear sound & noise cancellation]]></description>
84+
<notes>
85+
<!--Internal documentation only-->
86+
<?style bold?>Read the manual before use
87+
</notes>
88+
</product>
89+
```
90+
91+
## XML → JSON
92+
93+
The XML conversion functions offer two modes. The default “simple” mode strips away document-level metadata and returns only the element content as standard JSON. When you need lossless round-tripping, enable the “roundtrip” mode to capture declarations, DOCTYPE information, prolog/epilog nodes, and the precise ordering of mixed content.
94+
95+
### Simple conversion (default)
96+
97+
```ts
98+
import { xmlStringToJsonObject } from "typesxml";
99+
100+
101+
Simple mode is usually enough when you only need to round-trip the element structure and attribute values. It keeps the payload compact and still converts back to the same XML content, as long as document-level metadata or mixed-content ordering is not significant for your workflow.
102+
const xml: string = [
103+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
104+
"<!--Before root-->",
105+
"<libraryCatalog>",
106+
" <library category=\"memo\">painters</library>",
107+
" <books>",
108+
" <book>DaVinci</book>",
109+
" <book>VanGogh</book>",
110+
" <book>Rubens</book>",
111+
" </books>",
112+
"</libraryCatalog>"
113+
].join("\n");
114+
115+
const json: any = xmlStringToJsonObject(xml);
116+
console.log(JSON.stringify(json, null, 2));
117+
```
118+
119+
Output:
120+
121+
```json
122+
{
123+
"library": {
124+
"_attributes": {
125+
"category": "memo"
126+
},
127+
"_text": "painters"
128+
},
129+
"books": [
130+
"DaVinci",
131+
"VanGogh",
132+
"Rubens"
133+
]
134+
}
135+
```
136+
137+
Simple mode omits the XML declaration, DOCTYPE, and the root element name. Provide the root name yourself when converting back to XML. If the JSON object has exactly one top-level property, the converter uses that key automatically; otherwise call `jsonObjectToXmlDocument(json, "libraryCatalog")` (or whichever name matches your document).
138+
139+
### Round-trip mode
140+
141+
When lossless reconstruction is required, call the function with `mode: "roundtrip"`. The return value follows the `XmlJsonDocument` shape, bundling all metadata needed to rebuild the original document.
142+
143+
```ts
144+
const jsonDocument: any = xmlStringToJsonObject(xml, { mode: "roundtrip" });
145+
console.log(JSON.stringify(jsonDocument, null, 2));
146+
```
147+
148+
Result:
149+
150+
```json
151+
{
152+
"rootName": "libraryCatalog",
153+
"root": {
154+
"library": {
155+
"_attributes": {
156+
"category": "memo"
157+
},
158+
"_text": "painters"
159+
},
160+
"books": [
161+
"DaVinci",
162+
"VanGogh",
163+
"Rubens"
164+
],
165+
"_content": [
166+
{
167+
"kind": "text",
168+
"value": "\n "
169+
},
170+
{
171+
"kind": "element",
172+
"name": "library",
173+
"occurrence": 0
174+
},
175+
{
176+
"kind": "text",
177+
"value": "\n "
178+
},
179+
{
180+
"kind": "element",
181+
"name": "books",
182+
"occurrence": 0
183+
},
184+
{
185+
"kind": "text",
186+
"value": "\n"
187+
}
188+
]
189+
},
190+
"declaration": {
191+
"version": "1.0",
192+
"encoding": "UTF-8"
193+
},
194+
"prolog": [
195+
{
196+
"type": "text",
197+
"value": "\n"
198+
},
199+
{
200+
"type": "comment",
201+
"value": "Before root"
202+
},
203+
{
204+
"type": "text",
205+
"value": "\n"
206+
}
207+
]
208+
}
209+
```
210+
211+
`_content` lists the ordered mix of child nodes so round-tripping retains indentation, comments, CDATA sections, and processing instructions exactly where they appeared. Each document-level `prolog` (and `epilog`) entry includes the original whitespace as `type: "text"` items to keep formatting intact; when a comment or processing instruction followed the DOCTYPE declaration you will also see `afterDoctype: true` on that entry.
212+
213+
Use this mode when you must preserve declarations, DOCTYPE data, or the exact ordering of mixed content. It produces a richer JSON payload and is ideal for archival or editing tools that need to reconstruct the original bytes faithfully.
214+
215+
### Round-tripping documents
216+
217+
`jsonObjectToXmlDocument` works with either output. Supply the root element name when starting from the simple JSON value, or pass the round-trip payload untouched to restore every piece of metadata:
218+
219+
```ts
220+
import { jsonObjectToXmlDocument, xmlStringToJsonObject, XMLDocument } from "typesxml";
221+
222+
const simpleJson: any = xmlStringToJsonObject(xmlText);
223+
const rebuiltFromSimple: XMLDocument = jsonObjectToXmlDocument(simpleJson, "libraryCatalog");
224+
225+
const jsonDoc: any = xmlStringToJsonObject(xmlText, { mode: "roundtrip" });
226+
const rebuilt: XMLDocument = jsonObjectToXmlDocument(jsonDoc);
227+
console.log(rebuilt.equals(parseXml(xmlText))); // true for both
228+
```
229+
230+
### Plain object view
231+
232+
Simple mode gives you the element content directly, so you can treat the top-level value as the payload. When working with the round-trip structure, use the `root` property to access that same content. Arrays appear whenever the original XML contained repeated child elements with the singularised name (e.g. `<books>``<book>`). All text content is represented as strings; numbers and booleans are not automatically coerced.
233+
234+
## Advanced: SAX event arrays
235+
236+
TypesXML also exposes SAX event arrays for low-level stream control. Use those APIs when you prefer event-based processing or need to limit memory use. For details on SAX handlers and streaming, consult the advanced API reference together with the streaming sections of the [TypesXML Tutorial](tutorial.md).
237+
238+
## File and stream conversions
239+
240+
Every conversion function is also available for files and streams. Each variant accepts an optional options object; set `mode: "roundtrip"` when you need to retain metadata, or omit it for the lightweight default:
241+
242+
- `xmlFileToJsonObject(path, { mode: "roundtrip" })`, `xmlStreamToJsonObject(stream, { mode: "roundtrip" })``XmlJsonDocument` (simple mode when `mode` is omitted).
243+
- `xmlStringToJsonFile(xml, target, { mode: "roundtrip" })`, `xmlFileToJsonFile(path, target, encoding, indent, jsonEncoding, { mode: "roundtrip" })`, `xmlStreamToJsonFile(stream, target, { mode: "roundtrip" })` → write prettified JSON (default two-space indentation).
244+
- `jsonStringToXmlDocument`, `jsonFileToXmlDocument`, `jsonStreamToXmlDocument` → consume JSON and build `XMLDocument` instances.
245+
- `jsonObjectToXmlFile`, `jsonFileToXmlFile`, `jsonStreamToXmlFile` → produce XML output.
246+
247+
## Helper property reference
248+
249+
| Key | Purpose |
250+
| --- | --- |
251+
| `_attributes` | Attribute name/value map. |
252+
| `_text` | Literal text content (whitespace preserved). |
253+
| `_cdata` | CDATA section content, string or array. |
254+
| `_comments` | Comment text, string or array. |
255+
| `_processingInstructions` | Array of `{ target, data? }` instructions. |
256+
| `_content` | (Round-trip mode only) Ordered content metadata (`kind`, `name`, `occurrence`) used to restore exact node ordering. |
257+
258+
Any other property names are treated as child element names. Arrays mapped to those properties describe repeated children; primitives become text nodes inside a single child element.
259+
260+
With these conversion functions you can move between JSON and XML using the same data structures you already work with, while still retaining access to advanced XML features when you need them.

docs/tutorial.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# TypesXML Tutorial
22

3-
This tutorial walks through the most common workflows for the TypesXML toolkit. Each section builds on the previous one so you can quickly wire the parser into a TypeScript or Node.js project.
3+
This tutorial walks through the most common workflows for the TypesXML toolkit. Each section builds on the previous one so you can quickly wire the parser into a TypeScript or Node.js project. If you need to move between XML and JSON, pair this guide with the dedicated [JSON and XML Conversion Guide](jsonTutorial.md).
44

55
For a feature overview see the [project README](../README.md), and download the runnable snippets described here from the [`samples/` guide](../samples/README.md).
66

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "typesxml",
33
"productName": "TypesXML",
4-
"version": "1.14.0",
4+
"version": "1.15.0",
55
"description": "Open source XML library written in TypeScript",
66
"keywords": [
77
"XML",
@@ -10,11 +10,13 @@
1010
"SAX",
1111
"DTD",
1212
"Default attributes",
13-
"TypeScript"
13+
"TypeScript",
14+
"JSON"
1415
],
1516
"scripts": {
1617
"build": "tsc",
17-
"testDtd": "tsc && node dist/tests/DTDTestSuite.js"
18+
"testDtd": "tsc && node dist/tests/DTDTestSuite.js",
19+
"testJson": "tsc && node dist/tests/JsonConversionTest.js"
1820
},
1921
"author": {
2022
"name": "Rodolfo M. Raya",

samples/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
These TypeScript snippets mirror the scenarios covered in `docs/tutorial.md`. They import the library exactly as you would in an application that depends on the published npm package.
44

5-
Start with the [project README](../README.md) for a high-level overview, then follow the detailed walkthrough in [`docs/tutorial.md`](../docs/tutorial.md) if you need extra context while running these scripts.
5+
Start with the [project README](../README.md) for a high-level overview, then follow the detailed walkthrough in [`docs/tutorial.md`](../docs/tutorial.md) if you need extra context while running these scripts. For XML↔JSON workflows, pair these samples with the [JSON and XML Conversion Guide](../docs/jsonTutorial.md).
66

77
## Prerequisites
88

@@ -30,6 +30,7 @@ A DTD-backed pair—`xml/library-valid.xml` and `xml/library-invalid.xml`—demo
3030
- `relaxng-defaults.ts` – Resolve a RelaxNG grammar via catalog lookup and observe default attributes merged into the DOM.
3131
- `stream-parse.ts` – Fetch an XML document over HTTPS and process it as a stream.
3232
- `custom-handler.ts` – Implement a bespoke `ContentHandler` that logs SAX events.
33+
- `json-conversion.ts` – Convert between JSON and XML, comparing simple mode with the metadata-preserving round-trip mode.
3334

3435
Shortcut scripts are defined in `package.json`, e.g. `npm run parse-file` (which performs `tsc -p tsconfig.json` and then runs `node dist/parse-file.js`). Use `npm run build` if you want to compile everything ahead of time.
3536

@@ -39,6 +40,7 @@ To explore the most common scenarios directly:
3940
- `npm run stream-parse` – build the samples and fetch a remote XML document over HTTPS, printing the raw payload.
4041
- `npm run custom-handler` – build the samples and stream SAX events through the logging handler.
4142
- `npm run relaxng-defaults` – build the samples and parse `library-rng.xml`, showing RelaxNG defaults applied even without validation.
43+
- `npm run json-conversion` – build the samples and see JSON↔XML conversion in both simple and round-trip modes.
4244

4345
For the validation sample:
4446

0 commit comments

Comments
 (0)