beckchr / staxon

JSON via StAX
107 stars 47 forks source link

Issue writing xml result #31

Closed mavencode01 closed 8 years ago

mavencode01 commented 8 years ago

I'm trying to convert a json to xml but can't get the whole xml content....am not sure if am doing it wrong.

Here is a quick test:

String test  = "{\"result\": [{\"a\":1}, {\"b\":1}, {\"c\":1}, {\"d\":1}]}";
InputStream input = new ByteArrayInputStream(test.getBytes(StandardCharsets.UTF_8));

ByteArrayOutputStream output = new ByteArrayOutputStream();

JsonXMLConfig config = new JsonXMLConfigBuilder().multiplePI(false).build();
try {

    XMLStreamReader reader = new JsonXMLInputFactory(config).createXMLStreamReader(input);
    Source source = new StAXSource(reader);

    XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(output);
    Result result = new StAXResult(new PrettyXMLStreamWriter(writer));
    TransformerFactory.newInstance().newTransformer().transform(source, result);

} catch (XMLStreamException | FactoryConfigurationError | TransformerException | TransformerFactoryConfigurationError e) {      
    e.printStackTrace();
} finally {                     

    response =  output.toString(); 
    system.out.println(response);
    output.close();
    input.close();      
} 

Result

<?xml version="1.0" ?>
<result>
    <a>1</a>
</result>

The rest of the json is ignore...any idea to what might be wrong ?

mavencode01 commented 8 years ago

Okay I figured out the problem in my json.

I changed my json to this and it works now.

String test = "{\"root\": {\"result\": [{\"a\":1}, {\"b\":1}, {\"c\":1}, {\"d\":1}]}}";

That will now produce something similar to this

<root>
  <result>
       <a>1</a>
 </result>
  <result>
       <b>1</b>
 </result>
  <result>
       <c>1</c>
 </result>
  <result>
       <d>1</d>
 </result>
</root>

Hope it helps someone

beckchr commented 8 years ago

Hi!

You can achieve the same result without changing your input by specifying a "virtual root" element:

JsonXMLConfig config = new JsonXMLConfigBuilder().virtualRoot("root").multiplePI(false).build();
mavencode01 commented 8 years ago

Great, it works!

How about an object like this?

[{\"a\":1}, {\"b\":1}, {\"c\":1}, {\"d\":1}]

@beckchr Do you know if it's possible to skip the root node from printing ? I have a report that I need to generate and want to keep streaming to all the records to the file before finally closing the root element.

<result>
 <row 1/>
 <row 2/>
...
<row n/>
</result>
beckchr commented 8 years ago

If the StAXON reader is used to parse an array, it will map it to a sequence of documents, one for each array object. If you transform that to XML with XSLT, you'll either get multiple XML documents in a row or the transformer will stop with an error after the first document, depending on the implementation.

Anyway, I'd recommend using the StAX event API to do the conversion. It's faster and it's also easier to do some dirty tricks... The basic conversion is shown here.

In order to make the conversion work (i.e. produce a valid XML document), we need to

To remove the Start/EndDocument events from the input, we can use an XML event filter:

XMLInputFactory inputFactory = new JsonXMLInputFactory(config);
XMLEventReader reader = inputFactory.createXMLEventReader(input);
reader = inputFactory.createFilteredReader(reader, new EventFilter() {
  @Override
  public boolean accept(XMLEvent event) {
    return !event.isStartDocument() && !event.isEndDocument();
  }
});

On the output side, we need to create a series of events like this:

In code:

XMLEventWriter writer = XMLOutputFactory.newInstance().createXMLEventWriter(output);
writer = new PrettyXMLEventWriter(writer); // format output
writer.add(XMLEventFactory.newFactory().createStartDocument());
writer.add(XMLEventFactory.newFactory().createStartElement("", "", "root"));
writer.add(reader);
writer.add(XMLEventFactory.newFactory().createEndElement("", "", "root"));
writer.add(XMLEventFactory.newFactory().createEndDocument());

Say our configuration is like this:

JsonXMLConfig config = new JsonXMLConfigBuilder().multiplePI(false).build();

With input

[{\"a\":1}, {\"b\":2}, {\"c\":3}]

this will produce:

<?xml version='1.0' encoding='UTF-8'?>
<root>
  <a>1</a>
  <b>2</b>
  <c>3</c>
</root>

Using a configuration with a "virtual" root like

JsonXMLConfig config = new JsonXMLConfigBuilder().virtualRoot("result").multiplePI(false).build();

would produce

<?xml version='1.0' encoding='UTF-8'?>
<root>
  <result>
    <a>1</a>
  </result>
  <result>
    <b>2</b>
  </result>
  <result>
    <c>3</c>
  </result>
</root>

Full example is attached. Hope that helps.

JSONArray2XML.java.zip

mavencode01 commented 8 years ago

Thanks @beckchr, will update my implementation.