package org.exist.util.serializer.json; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import org.exist.storage.DBBroker; import org.exist.storage.serializers.EXistOutputKeys; import org.exist.storage.serializers.Serializer; import org.exist.xquery.ErrorCodes; import org.exist.xquery.XPathException; import org.exist.xquery.functions.array.ArrayType; import org.exist.xquery.functions.map.MapType; import org.exist.xquery.value.*; import org.xml.sax.SAXException; import org.xml.sax.SAXNotRecognizedException; import org.xml.sax.SAXNotSupportedException; import javax.xml.transform.OutputKeys; import java.io.IOException; import java.io.Writer; import java.util.Map; import java.util.Properties; /** * Called by {@link org.exist.util.serializer.XQuerySerializer} to serialize an XQuery sequence * to JSON. The JSON serializer differs from other serialization methods because it maps XQuery * data items to JSON. * * @author Wolf */ public class JSONSerializer { private final DBBroker broker; private final Properties outputProperties; public JSONSerializer(DBBroker broker, Properties outputProperties) { super(); this.broker = broker; this.outputProperties = outputProperties; } public void serialize(Sequence sequence, Writer writer) throws SAXException { JsonFactory factory = new JsonFactory(); try { JsonGenerator generator = factory.createGenerator(writer); generator.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); if ("yes".equals(outputProperties.getProperty(OutputKeys.INDENT, "no"))) { generator.useDefaultPrettyPrinter(); } if ("yes".equals(outputProperties.getProperty(EXistOutputKeys.ALLOW_DUPLICATE_NAMES, "yes"))) { generator.enable(JsonGenerator.Feature.STRICT_DUPLICATE_DETECTION); } else { generator.disable(JsonGenerator.Feature.STRICT_DUPLICATE_DETECTION); } serializeSequence(sequence, generator); generator.close(); } catch (IOException | XPathException e) { throw new SAXException(e.getMessage(), e); } } private void serializeSequence(Sequence sequence, JsonGenerator generator) throws IOException, XPathException, SAXException { if (sequence.isEmpty()) { generator.writeNull(); } else if (sequence.hasOne() && "no".equals(outputProperties.getProperty(EXistOutputKeys.JSON_ARRAY_OUTPUT, "no"))) { serializeItem(sequence.itemAt(0), generator); } else { generator.writeStartArray(); for (SequenceIterator i = sequence.iterate(); i.hasNext(); ) { serializeItem(i.nextItem(), generator); } generator.writeEndArray(); } } private void serializeItem(Item item, JsonGenerator generator) throws IOException, XPathException, SAXException { if (item.getType() == Type.ARRAY) { serializeArray((ArrayType) item, generator); } else if (item.getType() == Type.MAP) { serializeMap((MapType) item, generator); } else if (Type.subTypeOf(item.getType(), Type.ATOMIC)) { if (Type.subTypeOf(item.getType(), Type.NUMBER)) { generator.writeNumber(item.getStringValue()); } else { switch (item.getType()) { case Type.BOOLEAN: generator.writeBoolean(((AtomicValue)item).effectiveBooleanValue()); break; default: generator.writeString(item.getStringValue()); break; } } } else if (Type.subTypeOf(item.getType(), Type.NODE)) { serializeNode(item, generator); } } private void serializeNode(Item item, JsonGenerator generator) throws SAXException { final Serializer serializer = broker.getSerializer(); serializer.reset(); final Properties xmlOutput = new Properties(); xmlOutput.setProperty(OutputKeys.METHOD, outputProperties.getProperty(EXistOutputKeys.JSON_NODE_OUTPUT_METHOD, "xml")); xmlOutput.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); xmlOutput.setProperty(OutputKeys.INDENT, outputProperties.getProperty(OutputKeys.INDENT, "no")); try { serializer.setProperties(xmlOutput); generator.writeString(serializer.serialize((NodeValue)item)); } catch (IOException e) { throw new SAXException(e.getMessage(), e); } } private void serializeArray(ArrayType array, JsonGenerator generator) throws IOException, XPathException, SAXException { generator.writeStartArray(); for (int i = 0; i < array.getSize(); i++) { final Sequence member = array.get(i); serializeSequence(member, generator); } generator.writeEndArray(); } private void serializeMap(MapType map, JsonGenerator generator) throws IOException, XPathException, SAXException { generator.writeStartObject(); for (Map.Entry<AtomicValue, Sequence> entry: map) { generator.writeFieldName(entry.getKey().getStringValue()); serializeSequence(entry.getValue(), generator); } generator.writeEndObject(); } }