XML a JSON v Javě

Java nabízí DOM, SAX, StAX, XPath a XSD v modulu java.xml. JSON vyžaduje knihovnu, například Jackson. Ukázky používají standardní XML API JDK 25 a výslovně omezují externí zdroje.

Autor: Aktualizováno

Načtení XML

JDK 25; fragment v metodě s throws Exception
import java.io.StringReader;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import org.xml.sax.InputSource;

var factory = DocumentBuilderFactory.newDefaultNSInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
factory.setXIncludeAware(false);
var builder = factory.newDocumentBuilder();
var doc = builder.parse(new InputSource(new StringReader(
    "<objednavka><jmeno>Jana</jmeno></objednavka>")));
System.out.println(doc.getDocumentElement().getTextContent());

Factory s podporou jmenných prostorů vytvoří DOM dokument. Před čtením nastav zabezpečení; pokud některá volba na zvoleném procesoru není podporovaná, nepokračuj tiše s jiným chováním.

Vytvoření XML

var novy = builder.newDocument();
var root = novy.createElementNS("urn:objednavky", "o:objednavka");
novy.appendChild(root);
var jmeno = novy.createElementNS("urn:objednavky", "o:jmeno");
jmeno.setTextContent("Jana & Petr");
root.appendChild(jmeno);

createElementNS určuje URI i kvalifikovaný název. setTextContent escapování nevytváří ručně; to provede serializátor.

Serializace XML

var tf = javax.xml.transform.TransformerFactory.newDefaultInstance();
tf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
var transformer = tf.newTransformer();
transformer.setOutputProperty(javax.xml.transform.OutputKeys.ENCODING, "UTF-8");
transformer.transform(new javax.xml.transform.dom.DOMSource(novy),
    new javax.xml.transform.stream.StreamResult(System.out));

Při zápisu do OutputStream se použije uvedené kódování. Writer už rozhoduje o převodu znaků na bajty sám. Zápis může změnit například odsazení nebo podobu prázdných elementů, i když obsah dokumentu zůstane stejný.

DOM a mapování objektů

Document a Element jsou W3C DOM uzly v paměti. getElementsByTagNameNS hledá podle URI a lokální části. Pro mapování XML na Java objekty můžeš použít Jakarta XML Binding; JAXB není součástí moderního JDK a vyžaduje samostatné API a implementaci. Nemíchej javax.xml.bind se současným jakarta.xml.bind.

Streamové čtení: SAX a StAX

var inputFactory = javax.xml.stream.XMLInputFactory.newDefaultFactory();
inputFactory.setProperty(javax.xml.stream.XMLInputFactory.SUPPORT_DTD, false);
inputFactory.setProperty(javax.xml.stream.XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
inputFactory.setXMLResolver((publicId, systemId, base, namespace) -> {
    throw new javax.xml.stream.XMLStreamException("Externí zdroj zakázán");
});
var reader = inputFactory.createXMLStreamReader(new StringReader("<r><a>1</a></r>"));
try {
    while (reader.hasNext()) {
        if (reader.next() == javax.xml.stream.XMLStreamConstants.START_ELEMENT) {
            System.out.println(reader.getLocalName());
        }
    }
} finally { reader.close(); }

SAX volá obsluhy událostí, StAX nechá aplikaci tahat další událost. Text elementu může být rozdělen do více událostí. Nastavení DTD a externích entit proveď pro konkrétní factory; bezpečnost DOM se do StAX automaticky nepřenese.

XPath

var xp = javax.xml.xpath.XPathFactory.newDefaultInstance().newXPath();
System.out.println(xp.evaluate("string(/objednavka/jmeno)", doc));

Vestavěný procesor používá XPath 1.0. Pro prefixy nastav NamespaceContext; neprefixovaná cesta nevybere elementy ve výchozím prostoru. XPath 3.1 potřebuje jiný procesor.

Validace proti XSD

var sf = javax.xml.validation.SchemaFactory.newDefaultInstance();
sf.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
sf.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
var schema = sf.newSchema(new java.io.File("objednavka.xsd"));
var validator = schema.newValidator();
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
validator.validate(new javax.xml.transform.dom.DOMSource(doc));

Ukázka předpokládá místní objednavka.xsd a DOM z prvního příkladu. U import a include povol načítání jen předem známých schémat. JDK validátor není obecnou implementací XSD 1.1.

Bezpečné nastavení parseru

DTD, externí zdroje a XInclude jsou v prvním příkladu zakázané. Navíc nastav rozumné limity velikosti, hloubky a času podle aplikace. Zabezpeč samostatně parsování, validaci i transformaci podle JAXP Security Guide a kapitoly XXE.

Načtení JSON

Jackson 2.x; samostatná závislost jackson-databind
import com.fasterxml.jackson.databind.ObjectMapper;
var mapper = new ObjectMapper();
var data = mapper.readTree("{\"jmeno\":\"Jana\",\"vek\":20}");
System.out.println(data.path("jmeno").asText());

Tohle je API Jackson 2.x. Jackson 3 používá jiné balíčky a některá jiná API; nemíchej příklady různých hlavních verzí. Chybějící uzel se liší od explicitního null.

Vytvoření JSON

var data = mapper.createObjectNode();
data.put("jmeno", "Jana");
data.put("vek", 20);
data.putNull("telefon");

Serializace JSON

String text = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(data);

Pro peníze používej BigDecimal a definuj přenosovou konvenci i pro příjemce. Nezapínej neomezené polymorfní default typing pro cizí data.

JSON Schema

Jackson sám není plný JSON Schema validátor. Například networknt/json-schema-validator poskytuje validaci různých dialektů; vyber API podle hlavní verze knihovny a ověř podporu 2020-12. Generování schématu a validace dat jsou jiné operace.

Celý JSON program v Javě: soubor, schéma, změna a zápis

pom.xml — Jackson 2.x přichází jako závislost validátoru
<project xmlns="http://maven.apache.org/POM/4.0.0">
 <modelVersion>4.0.0</modelVersion><groupId>cz.xml.demo</groupId><artifactId>json-demo</artifactId><version>1.0</version>
 <properties><maven.compiler.release>17</maven.compiler.release><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties>
 <dependencies><dependency><groupId>com.networknt</groupId><artifactId>json-schema-validator</artifactId><version>2.0.4</version></dependency></dependencies>
 <build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.14.0</version></plugin></plugins></build>
</project>
kniha.json — v adresáři projektu
{"id":"001","nazev":"XML"}
src/main/java/JsonDemo.java — celý program, JDK 17+
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.InputFormat;
import com.networknt.schema.SchemaLocation;
import com.networknt.schema.SchemaRegistry;
import com.networknt.schema.SpecificationVersion;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

public class JsonDemo {
  public static void main(String[] args) throws Exception {
    var mapper = new ObjectMapper();
    var data = mapper.readTree(Files.readString(Path.of("kniha.json")));
    String schemaText = """
      {"$schema":"https://json-schema.org/draft/2020-12/schema",
       "type":"object","required":["id","nazev"],
       "properties":{"id":{"type":"string"},"nazev":{"type":"string"}},
       "additionalProperties":false}
      """;
    var registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_2020_12,
      builder -> builder.schemas(Map.of("urn:demo:kniha", schemaText)));
    var schema = registry.getSchema(SchemaLocation.of("urn:demo:kniha"));
    var errors = schema.validate(mapper.writeValueAsString(data), InputFormat.JSON);
    if (!errors.isEmpty()) throw new IllegalArgumentException(errors.toString());
    var output = mapper.createObjectNode();
    output.put("id", data.get("id").asText());
    output.put("nazev", "XML & JSON");
    mapper.writerWithDefaultPrettyPrinter().writeValue(Path.of("vystup.json").toFile(), output);
    System.out.println(output.get("id").asText());
    System.out.println(schema.validate("{\"id\":1,\"nazev\":\"XML\"}", InputFormat.JSON).isEmpty());
  }
}
Linux/macOS; na Windows odděl části classpath středníkem
mvn package dependency:copy-dependencies
java -cp "target/classes:target/dependency/*" JsonDemo

Program vypíše 001 a false a vytvoří vystup.json. Nejdříve parsuje text, potom ověří typy a povinná pole podle schématu, teprve potom upravuje model. Číselné id v původním souboru vyvolá validační chybu; false na konci je samostatný negativní test. Schéma je registrováno z místního řetězce pod URN, tento příklad nepotřebuje stahování $ref.

Ukázka připíná networknt 2.0.4 a používá SchemaRegistry API této řady s Jackson 2. Verze 1.x používají jiné názvy API a řada 3.x přechází na Jackson 3. U produkčního vstupu přidej limity velikosti a parseru, řízené načítání externích schémat a zpracování chyby zápisu. Dokumentace připnuté verze popisuje dialekty, formáty a výsledky validace.