JSON and XML tools
Format, minify and check JSON and XML with line and column of every error, validate JSON against a JSON Schema and XML against an XSD (documented subsets), query with JSONPath (RFC 9535) and XPath 1.0, and convert XML ↔ JSON and JSON ↔ CSV. Numbers are never rounded. Everything runs in your browser; nothing is uploaded.
Everything runs in this browser: texts and files are never uploaded or stored. Fields take up to 5 MB each; above 200 KB the results follow when typing stops. JSON is strict RFC 8259 (no comments, trailing commas or JSON5); numbers are copied exactly as written. XML: no external DTD or entity is ever loaded.
JSON text
Runs in this browser. Data is not uploaded or stored.
Check and format
Document and schema
“format” is only an annotation in draft 2020-12, so it is not checked unless you ask. Keywords this tool does not check are listed with the results, never skipped silently. $ref follows local JSON pointers (#/$defs/…, #/definitions/…); remote schemas are never loaded.
Runs in this browser. Data is not uploaded or stored.
Validation
Document and query
$.store.book[0].titlechild names and indices;$..authoranywhere below;[*]every child[-1]last item,[0:2]slice,[::-1]reversed,['a','b']several names[?@.price < 10 && @.isbn]filters;length(),count(),match(),search(),value()
Runs in this browser. Data is not uploaded or stored.
Matching nodes
XML document
Well-formedness as XML 1.0 with namespaces: one root, matching tags, quoted attributes, declared prefixes, valid characters and references. Entities: the five predefined ones, character references and text entities declared in the document's DOCTYPE (expanded up to 1,000,000 characters); external DTDs and entities are never loaded. Formatting changes only the white space between elements; mixed content, CDATA and xml:space="preserve" stay as written.
Runs in this browser. Data is not uploaded or stored.
Check and format
Document and expression
XPath 1.0 has no default namespace: elements in a namespace (xmlns="…") need a prefix in the expression, mapped here. Axes child, descendant, parent, ancestor, following(-sibling), preceding(-sibling), attribute, self and the -or-self forms; predicates; the whole function library (id() finds xml:id).
Runs in this browser. Data is not uploaded or stored.
Result
Document and schema
A documented subset of XML Schema 1.0 (see the Method): elements, sequence / choice / all, groups, any, occurrences, attributes, simple and complex content, derivation, and simple types with their facets. Imports, includes, list / union types, identity constraints, xsi:type and xsi:nil are listed as not checked; such a result is not a full validation.
Runs in this browser. Data is not uploaded or stored.
Validation
Convert
- XML ↔ JSON as xmltodict: attributes “@name”, text “#text”, repeated elements become arrays; XML values stay text. Comments, processing instructions, the DOCTYPE and the place of text between child elements are lost.
- JSON → CSV: an array of objects, one row each; nested objects become dotted columns (pos.x), arrays their JSON text. CSV → JSON: the first line names the fields.
Runs in this browser. Data is not uploaded or stored.
Result
How the checks work
Seven small tools for the JSON and XML that machines, MES, ERP and APIs exchange. Every part runs in this browser on the main thread (the page's security policy allows no separate worker in the offline file), so each field takes up to 5 MB of text; above 200 KB the results are recomputed when typing stops, and result tables show the first 500 rows (the CSV has all of them).
JSON: check, format, minify
- Strict JSON as RFC 8259 defines it: no comments, trailing commas, single quotes, NaN or JSON5. A syntax error is reported at the line and column where Python's json module reports it (the tests compare 53 texts), with the line and a caret under the place.
- Formatting from the text: strings and numbers are copied exactly as written (escapes, 1.50, 1e3, 12345678901234567890); only the white space between them changes (2 or 4 spaces, tabs, or none). Sort keys orders the members of every object by Unicode code points, as Python's sort_keys; a repeated key keeps both members, in their order.
- Numbers a double changes: a number is listed when the shortest text of the 64-bit double it becomes differs from its written value (an integer beyond ±(2^53 − 1), more digits than a double keeps, a value that becomes infinity or 0). JavaScript and many JSON libraries read numbers as doubles; send such values as strings when they must stay exact (serial numbers, counters, IDs).
JSON Schema
- Checked keywords (draft 2020-12 and draft-07): type, enum, const, multipleOf, minimum, maximum, exclusiveMinimum, exclusiveMaximum, minLength, maxLength, pattern, format (on request), minItems, maxItems, uniqueItems, items, prefixItems, contains, minContains, maxContains, minProperties, maxProperties, required, properties, patternProperties, additionalProperties, propertyNames, dependentRequired, dependentSchemas, allOf, anyOf, oneOf, not, if / then / else, $ref to a JSON pointer in the same schema (#, #/$defs/…, #/definitions/…), boolean schemas; for draft-07 also items as a list with additionalItems, dependencies, and $ref replacing its sibling keywords.
- Draft: from $schema (draft-04, -06 and -07 are read as draft-07), else 2020-12, or chosen. Not checked and listed: unevaluatedProperties / unevaluatedItems, $dynamicRef, $anchor, $id inside a schema, remote $ref (nothing is fetched), keywords of the other draft, unknown keywords (ignored, as validators do), formats outside the list below.
- Errors are reported where the Python package jsonschema reports them (the tests compare 815 cases): at the deepest value a keyword fails on, with the line and column of the value (of its key for object members), its path and the schema path. anyOf, oneOf, not and contains give one error at the value they apply to.
- format is an annotation in draft 2020-12, so it is checked only when you tick the box. Rules: date-time RFC 3339 date-time: 2026-10-02T08:00:00Z or with an offset ±hh:mm, optional fraction; T and Z in either case; a real calendar date; seconds up to 60 (leap second); date RFC 3339 full-date YYYY-MM-DD, a real calendar date; time RFC 3339 full-time hh:mm:ss with Z or an offset, optional fraction; email simplified RFC 5321 mailbox: dot-separated atoms @ a domain of letters, digits and hyphens (no quoted local parts, no IP literals); uri RFC 3986 URI with a scheme (scheme:rest), only URI characters, “%” followed by two hex digits; uuid RFC 4122 / 9562 form 8-4-4-4-12 hex digits, any version, either case; ipv4 dotted quad, each part 0–255 without leading zeros.
- multipleOf is decided on the decimal values as written (0.07 is a multiple of 0.01). jsonschema divides doubles and says it is not; the tests use values where both agree and check the decimal rule against Python's decimal module.
JSONPath (RFC 9535)
- The whole RFC: root $, child and descendant segments (.name, ..name, [ ]), names, wildcards, indices (negative from the end), slices [start:end:step], filters with ==, !=, <, <=, >, >=, &&, ||, ! and parentheses, and the functions length(), count(), match(), search() and value() with the RFC's type rules. match() and search() take I-Regexp (RFC 9485); ^ and $ act as anchors, as the compliance suite expects.
- Results are the matching nodes in document order with their Normalized Paths ($['readings'][0]['sensor']), line and column, and the values (numbers as written) as a JSON array. Checked against all 706 tests of the JSONPath Compliance Test Suite and the RFC's bookstore example run through the jsonpath-rfc9535 package.
XML: well-formedness and formatting
- XML 1.0 (fifth edition) with Namespaces in XML 1.0: one root element, matching end tags, quoted and unique attributes (also by namespace and local name), names and characters allowed by XML, references that resolve, declared prefixes, the xml and xmlns prefixes used correctly, the XML declaration only at the start. Errors are reported at the line and column where Python's expat reports them (columns count characters; the tests compare 1,000+ broken documents, most of them generated).
- Safe by design: no external DTD, external entity or schema is ever loaded (nothing is fetched, so no XXE). Entities: the five predefined ones, character references and text entities declared in the document's own DOCTYPE, expanded up to 1,000,000 characters in all (against “billion laughs”). Entities that contain markup, parameter entities and attribute defaults of a DTD are not applied, and the page says so.
- Formatting puts every element of element-only content on its own line; text of mixed content, CDATA sections and elements with xml:space="preserve" stay exactly as written; an element with nothing but white space inside becomes empty (<e/>). Character references are written as the characters they stand for, except where XML needs them (& < > and " , tab, line feed and carriage return in attributes). Formatting and minifying keep the tree: the tests parse the result again and compare it with lxml's reading of the input.
XPath 1.0
- The W3C XPath 1.0 language: location paths with every axis except namespace, node tests (name, prefix:name, *, prefix:*, node(), text(), comment(), processing-instruction('target')), predicates, unions, the operators and the whole core function library. Prefixes are mapped under Namespaces; a name without a prefix matches elements without a namespace only (XPath 1.0 has no default namespace). id() finds elements by xml:id, since no DTD is read.
- Checked against lxml (libxml2) on 174 expressions and 16 broken ones. Where libxml2 departs from the XPath 1.0 text, the tool follows the specification: a string with an exponent (number("1e3")) is NaN, and string() of a number is written without an exponent and with all the digits needed (libxml2 writes 15 significant digits and uses exponents for very large or small numbers).
XSD: the subset that is checked
- Structures: global and local element declarations (name, ref, type, inline types, minOccurs / maxOccurs, form, elementFormDefault, fixed values), complex types with sequence, choice, all, named groups and any (namespace, processContents strict / lax / skip), mixed and empty content, simpleContent extension and restriction, complexContent extension and restriction; attributes (type, use required / optional / prohibited, fixed, form, attributeFormDefault, ref, attribute groups); anyAttribute accepts undeclared attributes without checking them.
- Simple types: restrictions of the built-in types anySimpleType, string, normalizedString, token, language, Name, NCName, NMTOKEN, ID, IDREF, anyURI, boolean, decimal, integer, nonPositiveInteger, negativeInteger, nonNegativeInteger, positiveInteger, long, int, short, byte, unsignedLong, unsignedInt, unsignedShort, unsignedByte, double, float, date, dateTime, time, gYear, gYearMonth, duration, hexBinary, base64Binary with enumeration, pattern (XSD regular expressions translated, including \d, \w, \i and \c), length, minLength, maxLength, totalDigits, fractionDigits, whiteSpace, and minInclusive / maxInclusive / minExclusive / maxExclusive on numbers. Value spaces are compared exactly (decimals as decimals, 1.0 = 1).
- Not checked, always listed: xs:import, include and redefine (other files are never loaded), list and union types, identity constraints (key, keyref, unique), substitution groups and abstract elements, xsi:type and xsi:nil in the document, range facets on dates and times, patterns with block escapes (\p{IsBasicLatin}) or class subtraction, unknown built-in types. A result with such notes is not a full validation.
- Checked against lxml.etree.XMLSchema (libxml2): for every test document both agree on valid / not valid and on the line of the first error (libxml2 gives an element the line where its start tag ends). The tests cover the built-in types value by value, every facet, and 300 randomly changed documents of the example schema. One known difference: libxml2 accepts a double such as "1e" (an exponent without digits), which XML Schema does not allow; the tool refuses it. libxml2 also reads "\$" in a pattern as "$", which XSD 1.0 does not list; the tool does the same.
Conversions
- XML → JSON (the convention of the Python package xmltodict, checked against it): the root element becomes one member; an element without attributes and child elements becomes its trimmed text (null when empty); otherwise an object with "@attribute" members (namespace declarations included), one member per child element name in order of first appearance (an array when the name repeats, or always for the names you list), and "#text" for its text (all text pieces joined and trimmed). Values stay text. Lost: comments, processing instructions, the DOCTYPE, the order between different child elements and the places of text between them.
- JSON → XML (xmltodict's unparse, compared as trees): "@" members become attributes, "#text" the text (after the child elements), arrays repeated elements, null an empty element, numbers as written, true / false as text. A top-level array becomes <root> with one <item> per item, and anything but one object with one member is wrapped in <root> (both names can be changed). Member names must be XML names; an array directly inside an array and prefixes without an "@xmlns:prefix" member are refused with their place.
- JSON → CSV: one row per object of a top-level array (one object = one row; items that are not objects go to a column “value”), nested objects as dotted column names (pos.x), arrays as their JSON text, null as an empty cell; columns in order of first appearance; quoting as RFC 4180 with CR LF line ends (checked against Python's csv module). The downloaded file starts with a byte order mark so that Excel reads UTF-8.
- CSV → JSON: RFC 4180 cells (quotes, "" inside, line breaks in quoted cells), the delimiter from the first line or chosen, the first line as names (empty names become column_N, repeats get _2, _3 …). With types: JSON numbers stay as written (007 and 1,5 stay text), true / false, empty cells null, cells that are JSON arrays or objects become values; without: all text. Dotted names become nested objects. JSON → CSV → JSON gives the same data back for flat and nested objects.
Worked examples (the tool's examples)
- JSON: an illustrative machine status message on one line has 25 values, depth 3; the counter 12345678901234567890 is listed (a double reads 12345678901234567000) and the formatted text keeps it, as it keeps 41.50.
- JSON Schema: the same message with three mistakes gives 3 errors: “Runing” is not one of the allowed states (line 5), quality 1.02 is above the maximum 1 (line 7), unit “kpa” is not allowed (line 10), as jsonschema reports them.
- JSONPath:
$.readings[?@.value > 0].sensorfinds head-1-temp and feeder-07-count (the vacuum reading is negative). - XML: the illustrative bill of materials on one line is well-formed: 20 elements, 12 attributes, depth 3, one namespace.
- XPath: with b = urn:factokit:bom,
//b:item[b:qty > 2]/b:pnselects R-0402-10K and C-0603-100N. - XSD: with one quantity changed to −4 the document is not valid: 1 error on line 8 (“−4” must be above 0), as libxml2 reports it; the original document is valid.
- Convert: XML → JSON with item always an array gives three items; the four ref elements of the first become an array, and the comment is left out.
Common mistakes
- Reading large integers (IDs, serial numbers, counters) into JavaScript numbers: they are silently rounded. Keep them as strings or check them here.
- Writing //item for an element in a default namespace: XPath 1.0 needs a prefix.
- Relying on a “valid” result while the schema uses constructs that were listed as not checked.
- Expecting comments or the order of mixed elements to survive XML → JSON → XML.
- Opening a CSV written with a decimal comma: 1,5 stays text; choose “;” as the delimiter.
Files
- Every text field opens files (button or drag and drop) up to 5 MB: UTF-8 with or without a byte order mark, UTF-16 with one, the encoding an XML declaration names, otherwise Windows-1257. Files are read in this browser only and never uploaded.
- Downloads: the formatted or converted text, the results of every part as CSV (UTF-8 with a byte order mark; an “Example data” row while the example is shown), and the settings JSON (format factokit-json-xml, version 1, all texts and options of the seven parts), which can be imported again. Nothing is stored by this file.
How it was checked
- Independent Python references, written without reading this code: json (syntax errors, formatting), decimal (numbers), jsonschema 4.26 (draft 2020-12 and draft-07 errors), jsonpath-rfc9535 and the JSONPath Compliance Test Suite (BSD-2-Clause), expat (XML error positions), lxml / libxml2 (XML trees, XPath, XSD), xmltodict (XML ↔ JSON) and csv (CSV). The examples are illustrative data made up for FactoKit.
Not verified
- The specifications (RFC 8259, RFC 9535, RFC 9485, JSON Schema 2020-12 and draft-07, XML 1.0, Namespaces in XML, XPath 1.0, XML Schema 1.0) could not be read while this tool was built; the rules were written from memory and are confirmed by the independent implementations above, not by the texts. The format rules of the JSON Schema part are this tool's documented reading of RFC 3339, 5321, 3986 and 4122.
Questions
- Is my JSON or XML uploaded anywhere?
- No. Formatting, validation, queries and conversions run in your browser; the texts and files you open are not sent anywhere and are not stored. The results leave the page only as the files you download or the text you copy.
- Why does formatting not change my numbers?
- Many tools format JSON by reading it into JavaScript numbers and writing it back, which rounds integers beyond 2^53 (9007199254740993 becomes 9007199254740992) and long decimals, and rewrites 1.50 as 1.5. This tool formats from the text itself: only the white space between the tokens changes. It also lists the numbers that a reader using 64-bit doubles would change, so you can decide to send them as strings.
- Are comments or trailing commas allowed in the JSON?
- No. The tool reads strict JSON as RFC 8259 defines it, as most APIs and message brokers do: no comments, no trailing commas, no single quotes and no JSON5. The error message gives the line and column where a reader stops, at the same place as Python’s json module.
- Why does my XPath find nothing although the element is there?
- Usually because of a default namespace. In <bom xmlns="urn:factokit:bom"> every element is in that namespace, and XPath 1.0 has no default namespace, so //item looks for elements without a namespace. Map a prefix to the namespace URI (b = urn:factokit:bom) and write //b:item; the button “Use the document’s namespaces” fills the mapping for you.
- Is the XSD validation complete?
- It checks a documented subset of XML Schema 1.0 that covers most interface and configuration files: element structure, occurrences, attributes, simple types with facets, extension and restriction. Constructs outside the subset (xs:import, list and union types, keys and key references, xsi:type) are listed as not checked, so a “valid” result then means valid for the checked part only. For a final check use a full validator such as xmllint or your integration platform.
- How is XML converted to JSON?
- With the convention of the widely used xmltodict library: attributes become members named "@name", the text of an element with attributes or children becomes "#text", a repeated element becomes an array, and values stay text. Comments, processing instructions and the order between different child elements are not kept, so a round trip back to XML gives the same data but not always the same document.