jettison-json / jettison

Apache License 2.0
46 stars 28 forks source link

Json array with single element is serialized as an element and not as an array #1

Open sberyozkin opened 9 years ago

sberyozkin commented 9 years ago
Index: src/test/java/org/codehaus/jettison/mapped/MappedXMLStreamReaderTest.java =================================================================== --- src/test/java/org/codehaus/jettison/mapped/MappedXMLStreamReaderTest.java (revision 2709421) +++ src/test/java/org/codehaus/jettison/mapped/MappedXMLStreamReaderTest.java (revision 2714499) @@ -517,8 +517,12 @@ assertEquals(XMLStreamReader.START_ELEMENT, reader.next()); assertEquals("root", reader.getName().getLocalPart()); + assertEquals(XMLStreamReader.PROCESSING_INSTRUCTION, reader.next()); + assertEquals(XMLStreamReader.START_ELEMENT, reader.next()); assertEquals("root", reader.getName().getLocalPart()); + + assertEquals(XMLStreamReader.PROCESSING_INSTRUCTION, reader.next()); assertEquals(XMLStreamReader.START_ELEMENT, reader.next()); assertEquals("relationships", reader.getName().getLocalPart()); @@ -961,6 +965,7 @@ "}}"; // Expected XML String expXml = "" + + ""+ "" + "folder1" + "" + @@ -1012,6 +1017,8 @@ sb.append(mpdReader.getTextCharacters()); } else if (eventType == XMLStreamConstants.END_ELEMENT) { sb.append(""); + } else if (eventType == XMLStreamConstants.PROCESSING_INSTRUCTION) { + sb.append(""); } } mpdReader.close(); Index: src/test/java/org/codehaus/jettison/mapped/MappedXMLStreamConverterTest.java =================================================================== --- src/test/java/org/codehaus/jettison/mapped/MappedXMLStreamConverterTest.java (revision 0) +++ src/test/java/org/codehaus/jettison/mapped/MappedXMLStreamConverterTest.java (revision 2714499) @@ -0,0 +1,242 @@ +package org.codehaus.jettison.mapped; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +import org.codehaus.jettison.AbstractXMLStreamWriter; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; + +import junit.framework.TestCase; + +/**This class tests the transformation from JSON to XML and back to JSON using MappedXMLStreamReader and MappedXMLStreamWriter. + * There are two tests because there is a problem with reading root object with an array. + * Another root wrapping element is created and that's why the assert won't pass in single test case. + */ +public class MappedXMLStreamConverterTest extends TestCase { + + public static void testOneElementArrayConverter() throws JSONException { + + JSONObject[] originalJSONs = new JSONObject[8]; + + originalJSONs[0] = new JSONObject("{\"key\":\"value\"}"); + + originalJSONs[1] = new JSONObject("{ \"myRoot\": {" + " \"arr\" : " + "[{\"key\" : \"value\"}]}}"); + + originalJSONs[2] = new JSONObject("{ \"myRoot\": {" + " \"arr\" : " + "{\"key\" : [\"value\"]}," + + " \"arr1\" : " + "[{\"key1\" : \"value1\"}]}}"); + + originalJSONs[3] = new JSONObject("{ \"myRoot\": {" + " \"arr\" : " + + "[{\"key\" : \"value\"}, {\"key1\" : \"value1\"}]}}"); + + originalJSONs[4] = new JSONObject("{ \"myRoot\":" + "{\"key\" : \"value\"}}"); + + originalJSONs[5] = new JSONObject("{ \"myRoot\": " + "{\"key\" : [5]}}"); + + //with attribute + originalJSONs[6] = new JSONObject( + "{\"myRoot\" : {\"title\": [{\"@isbn\": \"15115115\",\"text\": \"This book is on XML and JSON()\"}] ,\"song\": {\"@isbn\": \"3333331\",\"text\": \"lalalalalalala\"}}}"); + + originalJSONs[7] = new JSONObject("{ \"myRoot\": {" + " \"arr\" : " + "[{\"key\" : {\"subkey\":[\"subvalue\"]}}]}}"); + + for (int i = 0; i < originalJSONs.length; i++) { + System.out.println("======================"); + System.out.println(i); + String originalJsonString = originalJSONs[i].toString(); + System.out.println("original JSON:"); + System.out.println(originalJsonString); + + try { + String xmlString = transformJSONToXML(originalJSONs[i]); + System.out.println("Transformed to XML:"); + System.out.println(xmlString); + + String transformedJSONString = transformXMLToJSON(xmlString); + System.out.println("Transformed back to JSON:"); + System.out.println(transformedJSONString); + + assertEquals(originalJsonString, transformedJSONString); + } catch (XMLStreamException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + public static void testOneElementArrayConverterWithRootArrayValue() throws JSONException { + JSONObject[] originalJSONs = new JSONObject[5]; + + originalJSONs[0] = new JSONObject("{ \"myRoot\": " + "[{\"key\" : \"value\"}]}"); + + originalJSONs[1] = new JSONObject("{ \"myRoot\": " + "[{\"key\" : [\"value\"]}, {\"key1\" : [\"value1\"]}]}"); + + originalJSONs[2] = new JSONObject("{ \"myRoot\": " + "[{\"key\" : [\"value\"]}]}"); + + originalJSONs[3] = new JSONObject("{ \"myRoot\": " + "[{\"key\" : \"value\"}, {\"key1\" : \"value1\"}]}"); + + originalJSONs[4] = new JSONObject("{ \"myRoot\": [{\"key\": [1,2,3]}] }"); + + for (int i = 0; i < originalJSONs.length; i++) { + System.out.println("======================"); + System.out.println(i); + System.out.println("original JSON:"); + System.out.println(originalJSONs[i]); + + try { + String xmlString = transformJSONToXML(originalJSONs[i]); + System.out.println("Transformed to XML:"); + System.out.println(xmlString); + + String transformedJSONString = transformXMLToJSON(xmlString); + System.out.println("Transformed back to JSON:"); + System.out.println(transformedJSONString); + + assertEquals("{\"myRoot\":" + originalJSONs[i].toString() + "}", transformedJSONString); + } catch (XMLStreamException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + public static String transformJSONToXML(JSONObject jsonObject) throws JSONException, XMLStreamException { + // json streamReader + MappedNamespaceConvention con = new MappedNamespaceConvention(); + XMLStreamReader reader = new MappedXMLStreamReader(jsonObject, con); + + // xml stream writer + StringWriter sw = new StringWriter(); + XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); + XMLStreamWriter xmlStreamWriter = outputFactory.createXMLStreamWriter(sw); + + while (reader.hasNext()) { + int x = reader.next(); + switch (x) { + case XMLStreamConstants.START_ELEMENT: + xmlStreamWriter.writeStartElement(reader.getPrefix(), reader.getLocalName(), + reader.getNamespaceURI()); + int namespaceCount = reader.getNamespaceCount(); + for (int i = namespaceCount - 1; i >= 0; i--) { + xmlStreamWriter.writeNamespace(reader.getNamespacePrefix(i), reader.getNamespaceURI(i)); + } + int attributeCount = reader.getAttributeCount(); + for (int i = 0; i < attributeCount; i++) { + xmlStreamWriter.writeAttribute(reader.getAttributePrefix(i), reader.getAttributeNamespace(i), + reader.getAttributeLocalName(i), reader.getAttributeValue(i)); + } + break; + case XMLStreamConstants.START_DOCUMENT: + break; + case XMLStreamConstants.CHARACTERS: + xmlStreamWriter.writeCharacters(reader.getText()); + break; + case XMLStreamConstants.CDATA: + xmlStreamWriter.writeCData(reader.getText()); + break; + case XMLStreamConstants.END_ELEMENT: + xmlStreamWriter.writeEndElement(); + break; + case XMLStreamConstants.END_DOCUMENT: + xmlStreamWriter.writeEndDocument(); + break; + case XMLStreamConstants.SPACE: + break; + case XMLStreamConstants.COMMENT: + xmlStreamWriter.writeComment(reader.getText()); + break; + case XMLStreamConstants.DTD: + xmlStreamWriter.writeDTD(reader.getText()); + break; + case XMLStreamConstants.PROCESSING_INSTRUCTION: + xmlStreamWriter.writeProcessingInstruction(reader.getPITarget(), reader.getPIData()); + break; + case XMLStreamConstants.ENTITY_REFERENCE: + xmlStreamWriter.writeEntityRef(reader.getLocalName()); + break; + default: + throw new RuntimeException("Error in converting JSON to XML"); + } + } + xmlStreamWriter.flush(); + xmlStreamWriter.close(); + + return sw.toString(); + } + + public static String transformXMLToJSON(String xml) throws XMLStreamException { + // xml stream reader + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + InputStream xmlInputStream = new ByteArrayInputStream(xml.toString().getBytes(StandardCharsets.UTF_8)); + XMLStreamReader streamReader = inputFactory.createXMLStreamReader(xmlInputStream); + + // json streamWriter + StringWriter strWriter = new StringWriter(); + MappedNamespaceConvention con = new MappedNamespaceConvention(); + AbstractXMLStreamWriter jettisonJsonWriter = new MappedXMLStreamWriter(con, strWriter); + + jettisonJsonWriter.writeStartDocument(); + while (streamReader.hasNext()) { + int x = streamReader.next(); + switch (x) { + case XMLStreamConstants.START_ELEMENT: + jettisonJsonWriter.writeStartElement(streamReader.getPrefix(), streamReader.getLocalName(), + streamReader.getNamespaceURI()); + int namespaceCount = streamReader.getNamespaceCount(); + for (int i = namespaceCount - 1; i >= 0; i--) { + jettisonJsonWriter.writeNamespace(streamReader.getNamespacePrefix(i), + streamReader.getNamespaceURI(i)); + } + int attributeCount = streamReader.getAttributeCount(); + for (int i = 0; i < attributeCount; i++) { + jettisonJsonWriter.writeAttribute(streamReader.getAttributePrefix(i), + streamReader.getAttributeNamespace(i), streamReader.getAttributeLocalName(i), + streamReader.getAttributeValue(i)); + } + break; + case XMLStreamConstants.START_DOCUMENT: + break; + case XMLStreamConstants.CHARACTERS: + jettisonJsonWriter.writeCharacters(streamReader.getText()); + break; + case XMLStreamConstants.CDATA: + jettisonJsonWriter.writeCData(streamReader.getText()); + break; + case XMLStreamConstants.END_ELEMENT: + jettisonJsonWriter.writeEndElement(); + break; + case XMLStreamConstants.END_DOCUMENT: + jettisonJsonWriter.writeEndDocument(); + break; + case XMLStreamConstants.SPACE: + break; + case XMLStreamConstants.COMMENT: + jettisonJsonWriter.writeComment(streamReader.getText()); + break; + case XMLStreamConstants.DTD: + jettisonJsonWriter.writeDTD(streamReader.getText()); + break; + case XMLStreamConstants.PROCESSING_INSTRUCTION: + jettisonJsonWriter.writeProcessingInstruction(streamReader.getPITarget(), streamReader.getPIData()); + break; + case XMLStreamConstants.ENTITY_REFERENCE: + jettisonJsonWriter.writeEntityRef(streamReader.getLocalName()); + break; + default: + throw new RuntimeException("Error in converting XML to Json"); + } + } + jettisonJsonWriter.flush(); + jettisonJsonWriter.close(); + + return strWriter.toString(); + } +} Index: src/main/java/org/codehaus/jettison/AbstractXMLStreamReader.java =================================================================== --- src/main/java/org/codehaus/jettison/AbstractXMLStreamReader.java (revision 2709421) +++ src/main/java/org/codehaus/jettison/AbstractXMLStreamReader.java (revision 2714499) @@ -26,6 +26,9 @@ protected int event; protected Node node; + /** boolean flag which indicates that the current element has one element array */ + protected boolean nodeWithOneElementArray; + public boolean isAttributeSpecified(int index) { return false; } @@ -187,11 +190,17 @@ } public String getPIData() { - return null; + if(nodeWithOneElementArray) { + return JettisonConstants.PI_DATA_ARRAY_WITH_ONE_ELEMENT; + } + return null; } public String getPITarget() { - return null; + if(nodeWithOneElementArray) { + return JettisonConstants.JETTISON_PROCESS_INSTRUCTION_TARGET; + } + return null; } public String getPrefix() { Index: src/main/java/org/codehaus/jettison/mapped/MappedXMLStreamWriter.java =================================================================== --- src/main/java/org/codehaus/jettison/mapped/MappedXMLStreamWriter.java (revision 2709421) +++ src/main/java/org/codehaus/jettison/mapped/MappedXMLStreamWriter.java (revision 2714499) @@ -23,6 +23,7 @@ import javax.xml.stream.XMLStreamException; import org.codehaus.jettison.AbstractXMLStreamWriter; +import org.codehaus.jettison.JettisonConstants; import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; @@ -41,7 +42,7 @@ private Stack stack = new Stack(); /** Element currently being processed. */ private JSONProperty current; - + /** * JSON property currently being constructed. For efficiency, this is * concretely represented as either a property with a String value or an @@ -50,6 +51,10 @@ private abstract class JSONProperty { private String key; private String parentKey; + + /** This is a flag which indicate if the next value is array with one element */ + private boolean isArrayWithOneElementFollow; + public JSONProperty(String key, String parentKey) { this.key = key; this.parentKey = parentKey; @@ -73,6 +78,12 @@ public JSONPropertyObject withProperty(JSONProperty property) { return withProperty(property, true); } + public boolean isArrayWithOneElementFollow(){ + return isArrayWithOneElementFollow; + } + public void setIsArrayWithOneElementFollow(boolean isArrayWithOneElementFollow){ + this.isArrayWithOneElementFollow = isArrayWithOneElementFollow; + } } /** @@ -112,6 +123,13 @@ if (add && value instanceof String && !emptyString) { value = convention.convertToJSONPrimitive((String)value); } + + if(isArrayWithOneElementFollow()) { + JSONArray arrayWithOneElement = new JSONArray(); + arrayWithOneElement.put(value); + value = arrayWithOneElement; + } + if (getSerializedAsArrays().contains(getPropertyArrayKey(property))) { JSONArray values = new JSONArray(); if (!convention.isIgnoreEmptyArrayValues() @@ -168,6 +186,16 @@ if(add && value instanceof String) { value = convention.convertToJSONPrimitive((String)value); } + if(isArrayWithOneElementFollow()) { + JSONArray arrayWithOneElement = new JSONArray(); + try { + object.put(property.getKey(), arrayWithOneElement); + } catch (JSONException e) { + e.printStackTrace(); + throw new AssertionError(e); + } + } + Object old = object.opt(property.getKey()); try { if(old != null) { @@ -354,8 +382,11 @@ } public void writeProcessingInstruction(String arg0, String arg1) throws XMLStreamException { - // TODO Auto-generated method stub - + if(arg0 != null && arg1!=null + && arg0.equals(JettisonConstants.JETTISON_PROCESS_INSTRUCTION_TARGET) + && arg1.equals(JettisonConstants.PI_DATA_ARRAY_WITH_ONE_ELEMENT)) { + current.setIsArrayWithOneElementFollow(true); + } } public MappedNamespaceConvention getConvention() { return convention; Index: src/main/java/org/codehaus/jettison/mapped/MappedXMLStreamReader.java =================================================================== --- src/main/java/org/codehaus/jettison/mapped/MappedXMLStreamReader.java (revision 2709421) +++ src/main/java/org/codehaus/jettison/mapped/MappedXMLStreamReader.java (revision 2714499) @@ -92,6 +92,13 @@ event = END_DOCUMENT; } } + } else if(event == PROCESSING_INSTRUCTION) { + //Process the case when the event is processing instruction + //Check if the previous node was node with one element array and disable the boolean flag + if(nodeWithOneElementArray) { + nodeWithOneElementArray = false; + } + processElement(); } // handle value in nodes with attributes if (nodes.size() > 0) { @@ -111,7 +118,6 @@ if (node.getArray() != null) { int index = node.getArrayIndex(); if (index >= node.getArray().length()) { - nodes.pop(); node = (Node) nodes.peek(); @@ -129,8 +135,8 @@ processElement(); } else { - event = END_ELEMENT; - node = (Node) nodes.pop(); + event = END_ELEMENT; + node = (Node) nodes.pop(); } return; } @@ -158,6 +164,14 @@ node.setArray(array); node.setArrayIndex(0); nodes.push(node); + + //Check if the array is with one element and handle this case + if(array.length() == 1) { + nodeWithOneElementArray = true; + event = PROCESSING_INSTRUCTION; + return; + } + processElement(); } return; Index: src/main/java/org/codehaus/jettison/JettisonConstants.java =================================================================== --- src/main/java/org/codehaus/jettison/JettisonConstants.java (revision 0) +++ src/main/java/org/codehaus/jettison/JettisonConstants.java (revision 2714499) @@ -0,0 +1,6 @@ +package org.codehaus.jettison; + +public interface JettisonConstants { + public static final String JETTISON_PROCESS_INSTRUCTION_TARGET = "jettison"; + public static final String PI_DATA_ARRAY_WITH_ONE_ELEMENT = "arrayWithOneElement"; +}
sberyozkin commented 9 years ago
Index: MappedXMLStreamWriter.java =================================================================== --- MappedXMLStreamWriter.java (revision 2714499) +++ MappedXMLStreamWriter.java (working copy) @@ -42,6 +42,8 @@ private Stack stack = new Stack(); /** Element currently being processed. */ private JSONProperty current; + /** boolean flag that indicates that the next element has array with one element */ + private boolean arrayWithOneElement; /** * JSON property currently being constructed. For efficiency, this is @@ -52,9 +54,6 @@ private String key; private String parentKey; - /** This is a flag which indicate if the next value is array with one element */ - private boolean isArrayWithOneElementFollow; - public JSONProperty(String key, String parentKey) { this.key = key; this.parentKey = parentKey; @@ -78,12 +77,6 @@ public JSONPropertyObject withProperty(JSONProperty property) { return withProperty(property, true); } - public boolean isArrayWithOneElementFollow(){ - return isArrayWithOneElementFollow; - } - public void setIsArrayWithOneElementFollow(boolean isArrayWithOneElementFollow){ - this.isArrayWithOneElementFollow = isArrayWithOneElementFollow; - } } /** @@ -124,12 +117,6 @@ value = convention.convertToJSONPrimitive((String)value); } - if(isArrayWithOneElementFollow()) { - JSONArray arrayWithOneElement = new JSONArray(); - arrayWithOneElement.put(value); - value = arrayWithOneElement; - } - if (getSerializedAsArrays().contains(getPropertyArrayKey(property))) { JSONArray values = new JSONArray(); if (!convention.isIgnoreEmptyArrayValues() @@ -186,15 +173,6 @@ if(add && value instanceof String) { value = convention.convertToJSONPrimitive((String)value); } - if(isArrayWithOneElementFollow()) { - JSONArray arrayWithOneElement = new JSONArray(); - try { - object.put(property.getKey(), arrayWithOneElement); - } catch (JSONException e) { - e.printStackTrace(); - throw new AssertionError(e); - } - } Object old = object.opt(property.getKey()); try { @@ -275,6 +253,11 @@ stack.push(current); String key = convention.createKey(prefix, ns, local); current = new JSONPropertyString(key, parentKey); + + if(arrayWithOneElement) { + serializeAsArray(current.getKey()); + arrayWithOneElement = false; + } } public void writeAttribute(String prefix, String ns, String local, String value) throws XMLStreamException { @@ -385,7 +368,7 @@ if(arg0 != null && arg1!=null && arg0.equals(JettisonConstants.JETTISON_PROCESS_INSTRUCTION_TARGET) && arg1.equals(JettisonConstants.PI_DATA_ARRAY_WITH_ONE_ELEMENT)) { - current.setIsArrayWithOneElementFollow(true); + arrayWithOneElement = true; } } public MappedNamespaceConvention getConvention() {
karendolan commented 5 years ago

Is this solved by https://github.com/jettison-json/jettison/issues/15?