From 0a20037e5f9af845b21eba890abc8059b6e63482 Mon Sep 17 00:00:00 2001 From: HamdiSaidaniX Date: Thu, 1 Jan 2026 21:09:49 +0100 Subject: [PATCH 1/8] refactor: extract xmlToJson into helper file and add tests --- __tests__/xml-to-json.test.ts | 144 +++++++++++++++++++++++++ components/seo/XmlToJsonSEO.tsx | 182 ++++++++++++++++++++++++++++++++ lib/xmlToJson.ts | 85 +++++++++++++++ pages/utilities/xml-to-json.tsx | 89 ++++++++++++++++ 4 files changed, 500 insertions(+) create mode 100644 __tests__/xml-to-json.test.ts create mode 100644 components/seo/XmlToJsonSEO.tsx create mode 100644 lib/xmlToJson.ts create mode 100644 pages/utilities/xml-to-json.tsx diff --git a/__tests__/xml-to-json.test.ts b/__tests__/xml-to-json.test.ts new file mode 100644 index 0000000..7097499 --- /dev/null +++ b/__tests__/xml-to-json.test.ts @@ -0,0 +1,144 @@ +/** + * @jest-environment jsdom + */ +import { xmlToJson } from "../lib/xmlToJson"; + +describe("XML to JSON Converter", () => { + describe("Basic conversion", () => { + it("should convert simple XML to JSON", () => { + const xml = "test"; + const result = xmlToJson(xml); + expect(result).toEqual({ root: { name: "test" } }); + }); + + it("should handle nested elements", () => { + const xml = "value"; + const result = xmlToJson(xml); + expect(result).toEqual({ + root: { parent: { child: "value" } }, + }); + }); + + it("should handle empty elements", () => { + const xml = ""; + const result = xmlToJson(xml); + expect(result).toEqual({ root: { empty: null } }); + }); + }); + + describe("Attribute handling", () => { + it("should convert XML attributes to @attributes object", () => { + const xml = 'test'; + const result = xmlToJson(xml); + expect(result).toEqual({ + root: { "@attributes": { id: "123" }, name: "test" }, + }); + }); + + it("should handle multiple attributes", () => { + const xml = ''; + const result = xmlToJson(xml); + expect(result).toEqual({ + root: { + "@attributes": { id: "123", class: "main", "data-test": "true" }, + }, + }); + }); + + it("should handle element with only attributes", () => { + const xml = ''; + const result = xmlToJson(xml); + expect(result).toEqual({ + root: { "@attributes": { id: "123" } }, + }); + }); + }); + + describe("Array handling", () => { + it("should convert multiple same-named elements to array", () => { + const xml = "123"; + const result = xmlToJson(xml); + expect(result).toEqual({ root: { item: ["1", "2", "3"] } }); + }); + + it("should handle mixed elements with arrays", () => { + const xml = + "test12"; + const result = xmlToJson(xml); + expect(result).toEqual({ + root: { name: "test", item: ["1", "2"] }, + }); + }); + }); + + describe("Text content handling", () => { + it("should handle text content with attributes", () => { + const xml = 'text content'; + const result = xmlToJson(xml); + expect(result).toEqual({ + root: { "@attributes": { id: "1" }, "#text": "text content" }, + }); + }); + + it("should trim whitespace from text content", () => { + const xml = " test "; + const result = xmlToJson(xml); + expect(result).toEqual({ root: { name: "test" } }); + }); + }); + + describe("Error handling", () => { + it("should throw error for invalid XML", () => { + const xml = ""; + expect(() => xmlToJson(xml)).toThrow("Invalid XML"); + }); + + it("should throw error for malformed XML", () => { + const xml = "not xml at all"; + expect(() => xmlToJson(xml)).toThrow("Invalid XML"); + }); + }); + + describe("Complex XML structures", () => { + it("should handle deeply nested structures", () => { + const xml = + "deep"; + const result = xmlToJson(xml); + expect(result).toEqual({ + root: { level1: { level2: { level3: "deep" } } }, + }); + }); + + it("should handle real-world XML example", () => { + const xml = ` + + + John + john@example.com + + + Jane + jane@example.com + + + `; + const result = xmlToJson(xml); + expect(result).toEqual({ + users: { + user: [ + { + "@attributes": { id: "1" }, + name: "John", + email: "john@example.com", + }, + { + "@attributes": { id: "2" }, + name: "Jane", + email: "jane@example.com", + }, + ], + }, + }); + }); + }); +}); diff --git a/components/seo/XmlToJsonSEO.tsx b/components/seo/XmlToJsonSEO.tsx new file mode 100644 index 0000000..29832f6 --- /dev/null +++ b/components/seo/XmlToJsonSEO.tsx @@ -0,0 +1,182 @@ +import Link from "next/link"; + +export default function XmlToJsonSEO() { + return ( +
+
+

+ Our free, open-source, and ad-free XML to JSON converter makes it + easy to transform your data formats. Convert configuration files, + API responses, or legacy XML data into modern JSON with just a few + clicks. Built with 💜 for developers by developers. +

+

+ Looking for YAML conversion instead? Check out{" "} + + jsontoyamlconverter.com + {" "} + for JSON ↔ YAML conversions. +

+
+ +
+

Why Convert XML to JSON?

+

+ XML (eXtensible Markup Language) has been a standard for data + exchange for decades, but JSON (JavaScript Object Notation) has + become the preferred format for modern web development. Converting{" "} + XML to JSON is essential when you need: +

+
    +
  • + Modern API Integration:
    Most modern REST APIs use + JSON, making conversion essential for integrating legacy XML data. +
  • +
  • + Reduced Data Size:
    JSON is more compact than XML, + reducing bandwidth and improving performance. +
  • +
  • + JavaScript Compatibility:
    JSON is native to + JavaScript, making it easier to work with in web applications. +
  • +
  • + Better Readability:
    JSON's simpler syntax makes data + easier to read and understand compared to verbose XML. +
  • +
+
+ +
+

How to Use Our XML to JSON Converter

+

Converting XML data to JSON has never been easier:

+
    +
  • + Step 1:
    Paste your XML code into the input box. +
  • +
  • + Step 2:
    Instantly receive your JSON output. No + registration or ads. +
  • +
  • + Step 3:
    Copy your JSON data and integrate it into + your project. +
  • +
+
+ +
+

Key Features of Our XML to JSON Tool

+
    +
  • + Client-side processing
    Your data never leaves your + browser - complete privacy guaranteed. +
  • +
  • + Attribute handling
    XML attributes are preserved as + @attributes in the JSON output. +
  • +
  • + Array detection
    Multiple elements with the same name + are automatically converted to arrays. +
  • +
  • + CDATA support
    CDATA sections are properly extracted + as text content. +
  • +
  • + Error detection
    Invalid XML is detected and reported + immediately. +
  • +
+
+ +
+

XML vs JSON: When to Use Each

+

+ Both XML and JSON have their strengths. Here's when to use each: +

+
    +
  • + XML:
    + Better for documents with mixed content, complex schemas, XSLT + transformations, and SOAP web services. +
  • +
  • + JSON:
    Preferred for REST APIs, configuration files, + web applications, and when file size matters. +
  • +
+
+ +
+

FAQs

+
    +
  • + What is XML?
    XML (eXtensible Markup Language) is a + markup language that defines rules for encoding documents in a + format that is both human-readable and machine-readable. +
  • +
  • + What is JSON?
    JSON (JavaScript Object Notation) is a + lightweight data format used to transmit data between servers and + web applications. +
  • +
  • + How are XML attributes handled?
    XML attributes are + converted to an @attributes object in the JSON output, preserving + all attribute data. +
  • +
  • + Can I convert JSON back to XML?
    While more complex + due to attribute handling, you can manually restructure JSON back + to XML format. +
  • +
  • + Is my data secure?
    Yes! All processing happens in + your browser. Your XML data is never sent to any server. +
  • +
  • + Need YAML conversion?
    For JSON to YAML conversion, + visit{" "} + + jsontoyamlconverter.com + + . +
  • +
+
+ +
+

Related Tools

+

Check out our other data conversion utilities:

+
    +
  • + JSON Formatter - + Format and beautify JSON data +
  • +
  • + YAML to JSON - Convert + YAML to JSON format +
  • +
  • + JSON to YAML - Convert + JSON to YAML format +
  • +
  • + CSV to JSON - Convert + CSV to JSON format +
  • +
+
+
+ ); +} diff --git a/lib/xmlToJson.ts b/lib/xmlToJson.ts new file mode 100644 index 0000000..bf34107 --- /dev/null +++ b/lib/xmlToJson.ts @@ -0,0 +1,85 @@ +/** + * Converts XML string to JSON object + * @param xml - The XML string to convert + * @returns The parsed JSON object + */ +export function xmlToJson(xml: string): unknown { + const parser = new DOMParser(); + const doc = parser.parseFromString(xml, "text/xml"); + + // Check for parsing errors + const parserError = doc.querySelector("parsererror"); + if (parserError) { + throw new Error("Invalid XML"); + } + + function nodeToJson(node: Node): unknown { + const obj: Record = {}; + + // Handle element nodes + if (node.nodeType === Node.ELEMENT_NODE) { + const element = node as Element; + + // Add attributes + if (element.attributes.length > 0) { + obj["@attributes"] = {}; + for (let i = 0; i < element.attributes.length; i++) { + const attr = element.attributes[i]; + (obj["@attributes"] as Record)[attr.nodeName] = + attr.nodeValue || ""; + } + } + + // Handle child nodes + if (element.hasChildNodes()) { + for (let i = 0; i < element.childNodes.length; i++) { + const child = element.childNodes[i]; + + // Text node + if ( + child.nodeType === Node.TEXT_NODE || + child.nodeType === Node.CDATA_SECTION_NODE + ) { + const text = child.textContent?.trim(); + if (text) { + // If only text content, return it directly + if ( + element.childNodes.length === 1 && + element.attributes.length === 0 + ) { + return text; + } + obj["#text"] = text; + } + } + // Element node + else if (child.nodeType === Node.ELEMENT_NODE) { + const childName = child.nodeName; + const childValue = nodeToJson(child); + + // Handle arrays (multiple elements with same name) + if (obj[childName] !== undefined) { + if (!Array.isArray(obj[childName])) { + obj[childName] = [obj[childName]]; + } + (obj[childName] as unknown[]).push(childValue); + } else { + obj[childName] = childValue; + } + } + } + } + + // If object has only @attributes, merge them + const keys = Object.keys(obj); + if (keys.length === 0) { + return null; + } + } + + return obj; + } + + const root = doc.documentElement; + return { [root.nodeName]: nodeToJson(root) }; +} diff --git a/pages/utilities/xml-to-json.tsx b/pages/utilities/xml-to-json.tsx new file mode 100644 index 0000000..c15069a --- /dev/null +++ b/pages/utilities/xml-to-json.tsx @@ -0,0 +1,89 @@ +import { useCallback, useState } from "react"; +import { Textarea } from "@/components/ds/TextareaComponent"; +import PageHeader from "@/components/PageHeader"; +import { Card } from "@/components/ds/CardComponent"; +import { Button } from "@/components/ds/ButtonComponent"; +import { Label } from "@/components/ds/LabelComponent"; +import Header from "@/components/Header"; +import { useCopyToClipboard } from "@/components/hooks/useCopyToClipboard"; +import { CMDK } from "@/components/CMDK"; +import CallToActionGrid from "@/components/CallToActionGrid"; +import XmlToJsonSEO from "@/components/seo/XmlToJsonSEO"; +import Meta from "../../components/Meta"; +import { xmlToJson } from "@/lib/xmlToJson"; + +export default function XMLtoJSON() { + const [input, setInput] = useState(""); + const [output, setOutput] = useState(""); + const { buttonText, handleCopy } = useCopyToClipboard(); + + const handleChange = useCallback( + (event: React.ChangeEvent) => { + const { value } = event.currentTarget; + setInput(value); + + if (!value.trim()) { + setOutput(""); + return; + } + + try { + const jsonObject = xmlToJson(value.trim()); + const output = JSON.stringify(jsonObject, null, 2); + setOutput(output); + } catch { + setOutput("Invalid XML input"); + } + }, + [] + ); + + return ( +
+ +
+ + +
+ +
+ +
+ +
+ +