package org.json.junit; /* Public Domain. */ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; import org.json.*; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; /** * Tests for JSON-Java XML.java * Note: noSpace() will be tested by JSONMLTest */ public class XMLTest { /** * JUnit supports temporary files and folders that are cleaned up after the test. * https://garygregory.wordpress.com/2010/01/20/junit-tip-use-rules-to-manage-temporary-files-and-folders/ */ @Rule public TemporaryFolder testFolder = new TemporaryFolder(); /** * JSONObject from a null XML string. * Expects a NullPointerException */ @Test(expected=NullPointerException.class) public void shouldHandleNullXML() { String xmlStr = null; JSONObject jsonObject = XML.toJSONObject(xmlStr); assertTrue("jsonObject should be empty", jsonObject.isEmpty()); } /** * Empty JSONObject from an empty XML string. */ @Test public void shouldHandleEmptyXML() { String xmlStr = ""; JSONObject jsonObject = XML.toJSONObject(xmlStr); assertTrue("jsonObject should be empty", jsonObject.isEmpty()); } /** * Empty JSONObject from a non-XML string. */ @Test public void shouldHandleNonXML() { String xmlStr = "{ \"this is\": \"not xml\"}"; JSONObject jsonObject = XML.toJSONObject(xmlStr); assertTrue("xml string should be empty", jsonObject.isEmpty()); } /** * Invalid XML string (tag contains a frontslash). * Expects a JSONException */ @Test public void shouldHandleInvalidSlashInTag() { String xmlStr = "\n"+ "\n"+ "
\n"+ " \n"+ " abc street\n"+ "
\n"+ "
"; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); } catch (JSONException e) { assertEquals("Expecting an exception message", "Misshaped tag at 176 [character 14 line 4]", e.getMessage()); } } /** * Invalid XML string ('!' char in tag) * Expects a JSONException */ @Test public void shouldHandleInvalidBangInTag() { String xmlStr = "\n"+ "\n"+ "
\n"+ " \n"+ " \n"+ "
\n"+ "
"; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); } catch (JSONException e) { assertEquals("Expecting an exception message", "Misshaped meta tag at 214 [character 12 line 7]", e.getMessage()); } } /** * Invalid XML string ('!' char and no closing tag brace) * Expects a JSONException */ @Test public void shouldHandleInvalidBangNoCloseInTag() { String xmlStr = "\n"+ "\n"+ "
\n"+ " \n"+ " \n"+ ""; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); } catch (JSONException e) { assertEquals("Expecting an exception message", "Misshaped meta tag at 213 [character 12 line 7]", e.getMessage()); } } /** * Invalid XML string (no end brace for tag) * Expects JSONException */ @Test public void shouldHandleNoCloseStartTag() { String xmlStr = "\n"+ "\n"+ "
\n"+ " \n"+ " \n"+ ""; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); } catch (JSONException e) { assertEquals("Expecting an exception message", "Misplaced '<' at 193 [character 4 line 6]", e.getMessage()); } } /** * Invalid XML string (partial CDATA chars in tag name) * Expects JSONException */ @Test public void shouldHandleInvalidCDATABangInTag() { String xmlStr = "\n"+ "\n"+ "
\n"+ " Joe Tester\n"+ " \n"+ "
\n"+ "
"; try { XML.toJSONObject(xmlStr); fail("Expecting a JSONException"); } catch (JSONException e) { assertEquals("Expecting an exception message", "Expected 'CDATA[' at 204 [character 11 line 5]", e.getMessage()); } } /** * Null JSONObject in XML.toString() */ @Test public void shouldHandleNullJSONXML() { JSONObject jsonObject= null; String actualXml=XML.toString(jsonObject); assertEquals("generated XML does not equal expected XML","\"null\"",actualXml); } /** * Empty JSONObject in XML.toString() */ @Test public void shouldHandleEmptyJSONXML() { JSONObject jsonObject= new JSONObject(); String xmlStr = XML.toString(jsonObject); assertTrue("xml string should be empty", xmlStr.isEmpty()); } /** * No SML start tag. The ending tag ends up being treated as content. */ @Test public void shouldHandleNoStartTag() { String xmlStr = "\n"+ "\n"+ "
\n"+ " \n"+ " >\n"+ "
\n"+ "
"; String expectedStr = "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\",\""+ "content\":\">\"},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+ "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; JSONObject jsonObject = XML.toJSONObject(xmlStr); JSONObject expectedJsonObject = new JSONObject(expectedStr); Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); } /** * Valid XML to JSONObject */ @Test public void shouldHandleSimpleXML() { String xmlStr = "\n"+ "\n"+ "
\n"+ " Joe Tester\n"+ " [CDATA[Baker street 5]\n"+ " \n"+ " true\n"+ " false\n"+ " null\n"+ " 42\n"+ " -23\n"+ " -23.45\n"+ " -23x.45\n"+ " 1, 2, 3, 4.1, 5.2\n"+ "
\n"+ "
"; // TODO: fails in strict mode because -23x.45 was not surrounded by quotes. // Should be split into a strictMode test, and a similar non-strictMode test String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"[CDATA[Baker street 5]\","+ "\"name\":\"Joe Tester\",\"NothingHere\":\"\",\"TrueValue\":true,\n"+ "\"FalseValue\":false,\"NullValue\":null,\"PositiveValue\":42,\n"+ "\"NegativeValue\":-23,\"DoubleValue\":-23.45,\"Nan\":\"-23x.45\",\n"+ "\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n"+ "},\"xsi:noNamespaceSchemaLocation\":"+ "\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/"+ "XMLSchema-instance\"}}"; compareStringToJSONObject(xmlStr, expectedStr); compareReaderToJSONObject(xmlStr, expectedStr); compareFileToJSONObject(xmlStr, expectedStr); } /** * Tests to verify that supported escapes in XML are converted to actual values. */ @Test public void testXmlEscapeToJson(){ String xmlStr = "\n"+ ""+ "\""+ "A €33"+ "A €22€"+ "some text ©"+ // TODO: Looks like a strictMode regression where embedded single quotes are not handled correctly // Should be fixed. // "" " & ' < >"+ "" " & < >"+ "𝄢 𐅥" + ""; String expectedStr = "{\"root\":{" + "\"rawQuote\":\"\\\"\"," + "\"euro\":\"A €33\"," + "\"euroX\":\"A €22€\"," + "\"unknown\":\"some text ©\"," + // TODO: Looks like a strictMode regression where embedded single quotes are not handled correctly // Should be fixed. // "\"known\":\"\\\" \\\" & ' < >\"," + "\"known\":\"\\\" \\\" & < >\"," + "\"high\":\"𝄢 𐅥\""+ "}}"; compareStringToJSONObject(xmlStr, expectedStr); compareReaderToJSONObject(xmlStr, expectedStr); compareFileToJSONObject(xmlStr, expectedStr); } /** * Tests that control characters are escaped. */ @Test public void testJsonToXmlEscape(){ // TODO: Looks like a strictMode regression where embedded single quotes are not handled correctly // Should be fixed. final String jsonSrc = "{\"amount\":\"10,00 €\"," + "\"description\":\"Ação Válida\u0085\"," // + "\"xmlEntities\":\"\\\" ' & < >\"" + "\"xmlEntities\":\"\\\" & < >\"" + "}"; JSONObject json = new JSONObject(jsonSrc); String xml = XML.toString(json); //test control character not existing assertFalse("Escaping \u0085 failed. Found in XML output.", xml.contains("\u0085")); assertTrue("Escaping \u0085 failed. Entity not found in XML output.", xml.contains("…")); // test normal unicode existing assertTrue("Escaping € failed. Not found in XML output.", xml.contains("€")); assertTrue("Escaping ç failed. Not found in XML output.", xml.contains("ç")); assertTrue("Escaping ã failed. Not found in XML output.", xml.contains("ã")); assertTrue("Escaping á failed. Not found in XML output.", xml.contains("á")); // test XML Entities converted assertTrue("Escaping \" failed. Not found in XML output.", xml.contains(""")); // TODO: restore when the regression is fixed // assertTrue("Escaping ' failed. Not found in XML output.", xml.contains("'")); assertTrue("Escaping & failed. Not found in XML output.", xml.contains("&")); assertTrue("Escaping < failed. Not found in XML output.", xml.contains("<")); assertTrue("Escaping > failed. Not found in XML output.", xml.contains(">")); } /** * Valid XML with comments to JSONObject */ @Test public void shouldHandleCommentsInXML() { String xmlStr = "\n"+ "\n"+ "\n"+ "
\n"+ " comment ]]>\n"+ " Joe Tester\n"+ " \n"+ " Baker street 5\n"+ "
\n"+ "
"; JSONObject jsonObject = XML.toJSONObject(xmlStr); String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"Baker "+ "street 5\",\"name\":\"Joe Tester\",\"content\":\" this is -- "+ " comment \"}}}"; JSONObject expectedJsonObject = new JSONObject(expectedStr); Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); } /** * Valid XML to XML.toString() */ @Test public void shouldHandleToString() { String xmlStr = "\n"+ "\n"+ "
\n"+ " [CDATA[Joe & T > e < s " t ' er]]\n"+ " Baker street 5\n"+ " 1, 2, 3, 4.1, 5.2\n"+ "
\n"+ "
"; String expectedStr = "{\"addresses\":{\"address\":{\"street\":\"Baker street 5\","+ "\"name\":\"[CDATA[Joe & T > e < s \\\" t \\\' er]]\","+ "\"ArrayOfNum\":\"1, 2, 3, 4.1, 5.2\"\n"+ "},\"xsi:noNamespaceSchemaLocation\":"+ "\"test.xsd\",\"xmlns:xsi\":\"http://www.w3.org/2001/"+ "XMLSchema-instance\"}}"; JSONObject jsonObject = XML.toJSONObject(xmlStr); String xmlToStr = XML.toString(jsonObject); JSONObject finalJsonObject = XML.toJSONObject(xmlToStr); JSONObject expectedJsonObject = new JSONObject(expectedStr); Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject); } /** * Converting a JSON doc containing '>' content to JSONObject, then * XML.toString() should result in valid XML. */ @Test public void shouldHandleContentNoArraytoString() { String expectedStr = "{\"addresses\":{\"content\":\">\"}}"; JSONObject expectedJsonObject = new JSONObject(expectedStr); String finalStr = XML.toString(expectedJsonObject); String expectedFinalStr = ">"; assertEquals("Should handle expectedFinal: ["+expectedStr+"] final: ["+ finalStr+"]", expectedFinalStr, finalStr); } /** * Converting a JSON doc containing a 'content' array to JSONObject, then * XML.toString() should result in valid XML. * TODO: This is probably an error in how the 'content' keyword is used. */ @Test public void shouldHandleContentArraytoString() { String expectedStr = "{\"addresses\":{" + "\"content\":[1, 2, 3]}}"; JSONObject expectedJsonObject = new JSONObject(expectedStr); String finalStr = XML.toString(expectedJsonObject); String expectedFinalStr = ""+ "1\n2\n3"; assertEquals("Should handle expectedFinal: ["+expectedStr+"] final: ["+ finalStr+"]", expectedFinalStr, finalStr); } /** * Converting a JSON doc containing a named array to JSONObject, then * XML.toString() should result in valid XML. */ @Test public void shouldHandleArraytoString() { String expectedStr = "{\"addresses\":{"+ "\"something\":[1, 2, 3]}}"; JSONObject expectedJsonObject = new JSONObject(expectedStr); String finalStr = XML.toString(expectedJsonObject); String expectedFinalStr = ""+ "123"+ ""; assertEquals("Should handle expectedFinal: ["+expectedStr+"] final: ["+ finalStr+"]", expectedFinalStr, finalStr); } /** * Tests that the XML output for empty arrays is consistent. */ @Test public void shouldHandleEmptyArray(){ final JSONObject jo1 = new JSONObject(); jo1.put("array",new Object[]{}); final JSONObject jo2 = new JSONObject(); jo2.put("array",new JSONArray()); final String expected = ""; String output1 = XML.toString(jo1,"jo"); assertEquals("Expected an empty root tag", expected, output1); String output2 = XML.toString(jo2,"jo"); assertEquals("Expected an empty root tag", expected, output2); } /** * Tests that the XML output for arrays is consistent when an internal array is empty. */ @Test public void shouldHandleEmptyMultiArray(){ final JSONObject jo1 = new JSONObject(); jo1.put("arr",new Object[]{"One", new String[]{}, "Four"}); final JSONObject jo2 = new JSONObject(); jo2.put("arr",new JSONArray(new Object[]{"One", new JSONArray(new String[]{}), "Four"})); final String expected = "OneFour"; String output1 = XML.toString(jo1,"jo"); assertEquals("Expected a matching array", expected, output1); String output2 = XML.toString(jo2,"jo"); assertEquals("Expected a matching array", expected, output2); } /** * Tests that the XML output for arrays is consistent when arrays are not empty. */ @Test public void shouldHandleNonEmptyArray(){ final JSONObject jo1 = new JSONObject(); jo1.put("arr",new String[]{"One", "Two", "Three"}); final JSONObject jo2 = new JSONObject(); jo2.put("arr",new JSONArray(new String[]{"One", "Two", "Three"})); final String expected = "OneTwoThree"; String output1 = XML.toString(jo1,"jo"); assertEquals("Expected a non empty root tag", expected, output1); String output2 = XML.toString(jo2,"jo"); assertEquals("Expected a non empty root tag", expected, output2); } /** * Tests that the XML output for arrays is consistent when arrays are not empty and contain internal arrays. */ @Test public void shouldHandleMultiArray(){ final JSONObject jo1 = new JSONObject(); jo1.put("arr",new Object[]{"One", new String[]{"Two", "Three"}, "Four"}); final JSONObject jo2 = new JSONObject(); jo2.put("arr",new JSONArray(new Object[]{"One", new JSONArray(new String[]{"Two", "Three"}), "Four"})); final String expected = "OneTwoThreeFour"; String output1 = XML.toString(jo1,"jo"); assertEquals("Expected a matching array", expected, output1); String output2 = XML.toString(jo2,"jo"); assertEquals("Expected a matching array", expected, output2); } /** * Converting a JSON doc containing a named array of nested arrays to * JSONObject, then XML.toString() should result in valid XML. */ @Test public void shouldHandleNestedArraytoString() { String xmlStr = "{\"addresses\":{\"address\":{\"name\":\"\",\"nocontent\":\"\","+ "\"outer\":[[1], [2], [3]]},\"xsi:noNamespaceSchemaLocation\":\"test.xsd\",\""+ "xmlns:xsi\":\"http://www.w3.org/2001/XMLSchema-instance\"}}"; JSONObject jsonObject = new JSONObject(xmlStr); String finalStr = XML.toString(jsonObject); JSONObject finalJsonObject = XML.toJSONObject(finalStr); String expectedStr = "
"+ "12"+ "3"+ "
test.xsdhttp://www.w3.org/2001/XMLSche"+ "ma-instance
"; JSONObject expectedJsonObject = XML.toJSONObject(expectedStr); Util.compareActualVsExpectedJsonObjects(finalJsonObject,expectedJsonObject); } /** * Possible bug: * Illegal node-names must be converted to legal XML-node-names. * The given example shows 2 nodes which are valid for JSON, but not for XML. * Therefore illegal arguments should be converted to e.g. an underscore (_). */ @Test public void shouldHandleIllegalJSONNodeNames() { JSONObject inputJSON = new JSONObject(); inputJSON.append("123IllegalNode", "someValue1"); inputJSON.append("Illegal@node", "someValue2"); String result = XML.toString(inputJSON); /* * This is invalid XML. Names should not begin with digits or contain * certain values, including '@'. One possible solution is to replace * illegal chars with '_', in which case the expected output would be: * <___IllegalNode>someValue1someValue2 */ String expected = "<123IllegalNode>someValue1someValue2"; assertEquals("length",expected.length(), result.length()); assertTrue("123IllegalNode",result.contains("<123IllegalNode>someValue1")); assertTrue("Illegal@node",result.contains("someValue2")); } /** * JSONObject with NULL value, to XML.toString() */ @Test public void shouldHandleNullNodeValue() { JSONObject inputJSON = new JSONObject(); inputJSON.put("nullValue", JSONObject.NULL); // This is a possible preferred result // String expectedXML = ""; /** * This is the current behavior. JSONObject.NULL is emitted as * the string, "null". */ String actualXML = "null"; String resultXML = XML.toString(inputJSON); assertEquals(actualXML, resultXML); } /** * Investigate exactly how the "content" keyword works */ @Test public void contentOperations() { /* * When a standalone 0) then return]]>"; JSONObject jsonObject = XML.toJSONObject(xmlStr); assertTrue("1. 3 items", 3 == jsonObject.length()); assertTrue("1. empty tag1", "".equals(jsonObject.get("tag1"))); assertTrue("1. empty tag2", "".equals(jsonObject.get("tag2"))); assertTrue("1. content found", "if (a < b && a > 0) then return".equals(jsonObject.get("content"))); // multiple consecutive standalone cdatas are accumulated into an array xmlStr = " 0) then return]]>"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("2. 3 items", 3 == jsonObject.length()); assertTrue("2. empty tag1", "".equals(jsonObject.get("tag1"))); assertTrue("2. empty tag2", "".equals(jsonObject.get("tag2"))); assertTrue("2. content array found", jsonObject.get("content") instanceof JSONArray); JSONArray jsonArray = jsonObject.getJSONArray("content"); assertTrue("2. array size", jsonArray.length() == 2); assertTrue("2. content array entry 0", "if (a < b && a > 0) then return".equals(jsonArray.get(0))); assertTrue("2. content array entry 1", "here is another cdata".equals(jsonArray.get(1))); /* * text content is accumulated in a "content" inside a local JSONObject. * If there is only one instance, it is saved in the context (a different JSONObject * from the calling code. and the content element is discarded. */ xmlStr = "value 1"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("3. 2 items", 1 == jsonObject.length()); assertTrue("3. value tag1", "value 1".equals(jsonObject.get("tag1"))); /* * array-style text content (multiple tags with the same name) is * accumulated in a local JSONObject with key="content" and value=JSONArray, * saved in the context, and then the local JSONObject is discarded. */ xmlStr = "value 12true"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("4. 1 item", 1 == jsonObject.length()); assertTrue("4. content array found", jsonObject.get("tag1") instanceof JSONArray); jsonArray = jsonObject.getJSONArray("tag1"); assertTrue("4. array size", jsonArray.length() == 3); assertTrue("4. content array entry 0", "value 1".equals(jsonArray.get(0))); assertTrue("4. content array entry 1", jsonArray.getInt(1) == 2); assertTrue("4. content array entry 2", jsonArray.getBoolean(2) == true); /* * Complex content is accumulated in a "content" field. For example, an element * may contain a mix of child elements and text. Each text segment is * accumulated to content. */ xmlStr = "val1val2"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("5. 1 item", 1 == jsonObject.length()); assertTrue("5. jsonObject found", jsonObject.get("tag1") instanceof JSONObject); jsonObject = jsonObject.getJSONObject("tag1"); assertTrue("5. 2 contained items", 2 == jsonObject.length()); assertTrue("5. contained tag", "".equals(jsonObject.get("tag2"))); assertTrue("5. contained content jsonArray found", jsonObject.get("content") instanceof JSONArray); jsonArray = jsonObject.getJSONArray("content"); assertTrue("5. array size", jsonArray.length() == 2); assertTrue("5. content array entry 0", "val1".equals(jsonArray.get(0))); assertTrue("5. content array entry 1", "val2".equals(jsonArray.get(1))); /* * If there is only 1 complex text content, then it is accumulated in a * "content" field as a string. */ xmlStr = "val1"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("6. 1 item", 1 == jsonObject.length()); assertTrue("6. jsonObject found", jsonObject.get("tag1") instanceof JSONObject); jsonObject = jsonObject.getJSONObject("tag1"); assertTrue("6. contained content found", "val1".equals(jsonObject.get("content"))); assertTrue("6. contained tag2", "".equals(jsonObject.get("tag2"))); /* * In this corner case, the content sibling happens to have key=content * We end up with an array within an array, and no content element. * This is probably a bug. */ xmlStr = "val1"; jsonObject = XML.toJSONObject(xmlStr); assertTrue("7. 1 item", 1 == jsonObject.length()); assertTrue("7. jsonArray found", jsonObject.get("tag1") instanceof JSONArray); jsonArray = jsonObject.getJSONArray("tag1"); assertTrue("array size 1", jsonArray.length() == 1); assertTrue("7. contained array found", jsonArray.get(0) instanceof JSONArray); jsonArray = jsonArray.getJSONArray(0); assertTrue("7. inner array size 2", jsonArray.length() == 2); assertTrue("7. inner array item 0", "val1".equals(jsonArray.get(0))); assertTrue("7. inner array item 1", "".equals(jsonArray.get(1))); /* * Confirm behavior of original issue */ String jsonStr = "{"+ "\"Profile\": {"+ "\"list\": {"+ "\"history\": {"+ "\"entries\": ["+ "{"+ "\"deviceId\": \"id\","+ "\"content\": {"+ "\"material\": ["+ "{"+ "\"stuff\": false"+ "}"+ "]"+ "}"+ "}"+ "]"+ "}"+ "}"+ "}"+ "}"; jsonObject = new JSONObject(jsonStr); xmlStr = XML.toString(jsonObject); /* * This is the created XML. Looks like content was mistaken for * complex (child node + text) XML. * * * * * id * {"material":[{"stuff":false}]} * * * * */ assertTrue("nothing to test here, see comment on created XML, above", true); } /** * Convenience method, given an input string and expected result, * convert to JSONObject and compare actual to expected result. * @param xmlStr the string to parse * @param expectedStr the expected JSON string */ private void compareStringToJSONObject(String xmlStr, String expectedStr) { JSONObject jsonObject = XML.toJSONObject(xmlStr); JSONObject expectedJsonObject = new JSONObject(expectedStr); Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); } /** * Convenience method, given an input string and expected result, * convert to JSONObject via reader and compare actual to expected result. * @param xmlStr the string to parse * @param expectedStr the expected JSON string */ private void compareReaderToJSONObject(String xmlStr, String expectedStr) { JSONObject expectedJsonObject = new JSONObject(expectedStr); Reader reader = new StringReader(xmlStr); JSONObject jsonObject = XML.toJSONObject(reader); Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); } /** * Convenience method, given an input string and expected result, convert to * JSONObject via file and compare actual to expected result. * * @param xmlStr * the string to parse * @param expectedStr * the expected JSON string * @throws IOException */ private void compareFileToJSONObject(String xmlStr, String expectedStr) { try { JSONObject expectedJsonObject = new JSONObject(expectedStr); File tempFile = this.testFolder.newFile("fileToJSONObject.xml"); FileWriter fileWriter = new FileWriter(tempFile); try { fileWriter.write(xmlStr); } finally { fileWriter.close(); } Reader reader = new FileReader(tempFile); try { JSONObject jsonObject = XML.toJSONObject(reader); Util.compareActualVsExpectedJsonObjects(jsonObject,expectedJsonObject); } finally { reader.close(); } } catch (IOException e) { fail("Error: " +e.getMessage()); } } /** * JSON string lost leading zero and converted "True" to true. */ @Test public void testToJSONArray_jsonOutput() { final String originalXml = "011000True"; final JSONObject expectedJson = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",1,\"00\",0],\"title\":true}}"); final JSONObject actualJsonOutput = XML.toJSONObject(originalXml, false); Util.compareActualVsExpectedJsonObjects(actualJsonOutput,expectedJson); } /** * JSON string cannot be reverted to original xml. */ @Test public void testToJSONArray_reversibility() { final String originalXml = "011000True"; final String revertedXml = XML.toString(XML.toJSONObject(originalXml, false)); assertNotEquals(revertedXml, originalXml); } /** * test passes when using the new method toJsonArray. */ @Test public void testToJsonXML() { final String originalXml = "011000True"; final JSONObject expected = new JSONObject("{\"root\":{\"item\":{\"id\":\"01\"},\"id\":[\"01\",\"1\",\"00\",\"0\"],\"title\":\"True\"}}"); final JSONObject actual = XML.toJSONObject(originalXml,true); Util.compareActualVsExpectedJsonObjects(actual, expected); final String reverseXml = XML.toString(actual); // this reversal isn't exactly the same. use JSONML for an exact reversal // the order of the elements may be differnet as well. final String expectedReverseXml = "01011000True"; assertEquals("length",expectedReverseXml.length(), reverseXml.length()); assertTrue("array contents", reverseXml.contains("011000")); assertTrue("item contents", reverseXml.contains("01")); assertTrue("title contents", reverseXml.contains("True")); } /** * test to validate certain conditions of XML unescaping. */ @Test public void testUnescape() { assertEquals("{\"xml\":\"Can cope <;\"}", XML.toJSONObject("Can cope <; ").toString()); assertEquals("Can cope <; ", XML.unescape("Can cope <; ")); assertEquals("{\"xml\":\"Can cope & ;\"}", XML.toJSONObject("Can cope & ; ").toString()); assertEquals("Can cope & ; ", XML.unescape("Can cope & ; ")); assertEquals("{\"xml\":\"Can cope &;\"}", XML.toJSONObject("Can cope &; ").toString()); assertEquals("Can cope &; ", XML.unescape("Can cope &; ")); // unicode entity assertEquals("{\"xml\":\"Can cope 4;\"}", XML.toJSONObject("Can cope 4; ").toString()); assertEquals("Can cope 4; ", XML.unescape("Can cope 4; ")); // double escaped assertEquals("{\"xml\":\"Can cope <\"}", XML.toJSONObject("Can cope &lt; ").toString()); assertEquals("Can cope < ", XML.unescape("Can cope &lt; ")); assertEquals("{\"xml\":\"Can cope 4\"}", XML.toJSONObject("Can cope &#x34; ").toString()); assertEquals("Can cope 4 ", XML.unescape("Can cope &#x34; ")); } /** * test passes when xsi:nil="true" converting to null (JSON specification-like nil conversion enabled) */ @Test public void testToJsonWithNullWhenNilConversionEnabled() { final String originalXml = ""; final String expectedJsonString = "{\"root\":{\"id\":null}}"; final JSONObject json = XML.toJSONObject(originalXml, new XMLParserConfiguration() .withKeepStrings(false) .withcDataTagName("content") .withConvertNilAttributeToNull(true)); assertEquals(expectedJsonString, json.toString()); } /** * test passes when xsi:nil="true" not converting to null (JSON specification-like nil conversion disabled) */ @Test public void testToJsonWithNullWhenNilConversionDisabled() { final String originalXml = ""; final String expectedJsonString = "{\"root\":{\"id\":{\"xsi:nil\":true}}}"; final JSONObject json = XML.toJSONObject(originalXml, new XMLParserConfiguration()); assertEquals(expectedJsonString, json.toString()); } /** * Tests to verify that supported escapes in XML are converted to actual values. */ @Test public void testIssue537CaseSensitiveHexEscapeMinimal(){ String xmlStr = "\n"+ "Neutrophils.Hypersegmented | Bld-Ser-Plas"; String expectedStr = "{\"root\":\"Neutrophils.Hypersegmented | Bld-Ser-Plas\"}"; JSONObject xmlJSONObj = XML.toJSONObject(xmlStr, true); JSONObject expected = new JSONObject(expectedStr); Util.compareActualVsExpectedJsonObjects(xmlJSONObj, expected); } /** * Tests to verify that supported escapes in XML are converted to actual values. */ @Test public void testIssue537CaseSensitiveHexEscapeFullFile(){ try { InputStream xmlStream = null; try { xmlStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue537.xml"); Reader xmlReader = new InputStreamReader(xmlStream, Charset.forName("UTF-8")); JSONObject actual = XML.toJSONObject(xmlReader, true); InputStream jsonStream = null; try { jsonStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue537.json"); final JSONObject expected = new JSONObject(new JSONTokener(jsonStream)); Util.compareActualVsExpectedJsonObjects(actual,expected); } finally { if (jsonStream != null) { jsonStream.close(); } } } finally { if (xmlStream != null) { xmlStream.close(); } } } catch (IOException e) { fail("file writer error: " +e.getMessage()); } } /** * Tests to verify that supported escapes in XML are converted to actual values. */ @Test public void testIssue537CaseSensitiveHexUnEscapeDirect(){ String origStr = "Neutrophils.Hypersegmented | Bld-Ser-Plas"; String expectedStr = "Neutrophils.Hypersegmented | Bld-Ser-Plas"; String actualStr = XML.unescape(origStr); assertEquals("Case insensitive Entity unescape", expectedStr, actualStr); } /** * test passes when xsi:type="java.lang.String" not converting to string */ @Test public void testToJsonWithTypeWhenTypeConversionDisabled() { String originalXml = "1234"; String expectedJsonString = "{\"root\":{\"id\":{\"xsi:type\":\"string\",\"content\":1234}}}"; JSONObject expectedJson = new JSONObject(expectedJsonString); JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration()); Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); } /** * test passes when xsi:type="java.lang.String" converting to String */ @Test public void testToJsonWithTypeWhenTypeConversionEnabled() { String originalXml = "1234" + "1234"; String expectedJsonString = "{\"root\":{\"id2\":1234,\"id1\":\"1234\"}}"; JSONObject expectedJson = new JSONObject(expectedJsonString); Map> xsiTypeMap = new HashMap>(); xsiTypeMap.put("string", new XMLXsiTypeConverter() { @Override public String convert(final String value) { return value; } }); xsiTypeMap.put("integer", new XMLXsiTypeConverter() { @Override public Integer convert(final String value) { return Integer.valueOf(value); } }); JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap)); Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); } @Test public void testToJsonWithXSITypeWhenTypeConversionEnabled() { String originalXml = "1234554321"; String expectedJsonString = "{\"root\":{\"asString\":\"12345\",\"asInt\":54321}}"; JSONObject expectedJson = new JSONObject(expectedJsonString); Map> xsiTypeMap = new HashMap>(); xsiTypeMap.put("string", new XMLXsiTypeConverter() { @Override public String convert(final String value) { return value; } }); xsiTypeMap.put("integer", new XMLXsiTypeConverter() { @Override public Integer convert(final String value) { return Integer.valueOf(value); } }); JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap)); Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); } @Test public void testToJsonWithXSITypeWhenTypeConversionNotEnabledOnOne() { String originalXml = "1234554321"; String expectedJsonString = "{\"root\":{\"asString\":\"12345\",\"asInt\":54321}}"; JSONObject expectedJson = new JSONObject(expectedJsonString); Map> xsiTypeMap = new HashMap>(); xsiTypeMap.put("string", new XMLXsiTypeConverter() { @Override public String convert(final String value) { return value; } }); JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap)); Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); } @Test public void testXSITypeMapNotModifiable() { Map> xsiTypeMap = new HashMap>(); XMLParserConfiguration config = new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap); xsiTypeMap.put("string", new XMLXsiTypeConverter() { @Override public String convert(final String value) { return value; } }); assertEquals("Config Conversion Map size is expected to be 0", 0, config.getXsiTypeMap().size()); try { config.getXsiTypeMap().put("boolean", new XMLXsiTypeConverter() { @Override public Boolean convert(final String value) { return Boolean.valueOf(value); } }); fail("Expected to be unable to modify the config"); } catch (Exception ignored) { } } @Test public void testIndentComplicatedJsonObject(){ String str = "{\n" + " \"success\": true,\n" + " \"error\": null,\n" + " \"response\": [\n" + " {\n" + " \"timestamp\": 1664917200,\n" + " \"dateTimeISO\": \"2022-10-05T00:00:00+03:00\",\n" + " \"loc\": {\n" + " \"lat\": 39.91987,\n" + " \"long\": 32.85427\n" + " },\n" + " \"place\": {\n" + " \"name\": \"ankara\",\n" + " \"state\": \"an\",\n" + " \"country\": \"tr\"\n" + " },\n" + " \"profile\": {\n" + " \"tz\": \"Europe/Istanbul\"\n" + " },\n" + " \"sun\": {\n" + " \"rise\": 1664941721,\n" + " \"riseISO\": \"2022-10-05T06:48:41+03:00\",\n" + " \"set\": 1664983521,\n" + " \"setISO\": \"2022-10-05T18:25:21+03:00\",\n" + " \"transit\": 1664962621,\n" + " \"transitISO\": \"2022-10-05T12:37:01+03:00\",\n" + " \"midnightSun\": false,\n" + " \"polarNight\": false,\n" + " \"twilight\": {\n" + " \"civilBegin\": 1664940106,\n" + " \"civilBeginISO\": \"2022-10-05T06:21:46+03:00\",\n" + " \"civilEnd\": 1664985136,\n" + " \"civilEndISO\": \"2022-10-05T18:52:16+03:00\",\n" + " \"nauticalBegin\": 1664938227,\n" + " \"nauticalBeginISO\": \"2022-10-05T05:50:27+03:00\",\n" + " \"nauticalEnd\": 1664987015,\n" + " \"nauticalEndISO\": \"2022-10-05T19:23:35+03:00\",\n" + " \"astronomicalBegin\": 1664936337,\n" + " \"astronomicalBeginISO\": \"2022-10-05T05:18:57+03:00\",\n" + " \"astronomicalEnd\": 1664988905,\n" + " \"astronomicalEndISO\": \"2022-10-05T19:55:05+03:00\"\n" + " }\n" + " },\n" + " \"moon\": {\n" + " \"rise\": 1664976480,\n" + " \"riseISO\": \"2022-10-05T16:28:00+03:00\",\n" + " \"set\": 1664921520,\n" + " \"setISO\": \"2022-10-05T01:12:00+03:00\",\n" + " \"transit\": 1664994240,\n" + " \"transitISO\": \"2022-10-05T21:24:00+03:00\",\n" + " \"underfoot\": 1664949360,\n" + " \"underfootISO\": \"2022-10-05T08:56:00+03:00\",\n" + " \"phase\": {\n" + " \"phase\": 0.3186,\n" + " \"name\": \"waxing gibbous\",\n" + " \"illum\": 71,\n" + " \"age\": 9.41,\n" + " \"angle\": 0.55\n" + " }\n" + " }\n" + " }\n" + " ]\n" + "}" ; JSONObject jsonObject = new JSONObject(str); String actualIndentedXmlString = XML.toString(jsonObject, 1); JSONObject actualJsonObject = XML.toJSONObject(actualIndentedXmlString); String expected = "true\n" + "\n" + " 2022-10-05T00:00:00+03:00\n" + " \n" + " 39.91987\n" + " 32.85427\n" + " \n" + " \n" + " \n" + " 0.3186\n" + " waxing gibbous\n" + " 0.55\n" + " 71\n" + " 9.41\n" + " \n" + " 2022-10-05T01:12:00+03:00\n" + " 1664949360\n" + " 1664921520\n" + " 1664994240\n" + " 2022-10-05T21:24:00+03:00\n" + " 2022-10-05T16:28:00+03:00\n" + " 1664976480\n" + " 2022-10-05T08:56:00+03:00\n" + " \n" + " \n" + " Europe/Istanbul\n" + " \n" + " \n" + " tr\n" + " ankara\n" + " an\n" + " \n" + " \n" + " 2022-10-05T18:25:21+03:00\n" + " false\n" + " 1664983521\n" + " 1664962621\n" + " false\n" + " 2022-10-05T12:37:01+03:00\n" + " 2022-10-05T06:48:41+03:00\n" + " 1664941721\n" + " \n" + " 1664985136\n" + " 1664936337\n" + " 1664988905\n" + " 2022-10-05T05:18:57+03:00\n" + " 1664940106\n" + " 2022-10-05T19:23:35+03:00\n" + " 2022-10-05T19:55:05+03:00\n" + " 1664938227\n" + " 1664987015\n" + " 2022-10-05T05:50:27+03:00\n" + " 2022-10-05T06:21:46+03:00\n" + " 2022-10-05T18:52:16+03:00\n" + " \n" + " \n" + " 1664917200\n" + "\n" + "null\n"; JSONObject expectedJsonObject = XML.toJSONObject(expected); assertTrue(expectedJsonObject.similar(actualJsonObject)); } @Test public void shouldCreateExplicitEndTagWithEmptyValueWhenConfigured(){ String jsonString = "{\"outer\":{\"innerOne\":\"\", \"innerTwo\":\"two\"}}"; JSONObject jsonObject = new JSONObject(jsonString); String expectedXmlString = "two"; String xmlForm = XML.toString(jsonObject,"encloser", new XMLParserConfiguration().withCloseEmptyTag(true)); JSONObject actualJsonObject = XML.toJSONObject(xmlForm); JSONObject expectedJsonObject = XML.toJSONObject(expectedXmlString); assertTrue(expectedJsonObject.similar(actualJsonObject)); } @Test public void shouldNotCreateExplicitEndTagWithEmptyValueWhenNotConfigured(){ String jsonString = "{\"outer\":{\"innerOne\":\"\", \"innerTwo\":\"two\"}}"; JSONObject jsonObject = new JSONObject(jsonString); String expectedXmlString = "two"; String xmlForm = XML.toString(jsonObject,"encloser", new XMLParserConfiguration().withCloseEmptyTag(false)); JSONObject actualJsonObject = XML.toJSONObject(xmlForm); JSONObject expectedJsonObject = XML.toJSONObject(expectedXmlString); assertTrue(expectedJsonObject.similar(actualJsonObject)); } @Test public void testIndentSimpleJsonObject(){ String str = "{ \"employee\": { \n" + " \"name\": \"sonoo\", \n" + " \"salary\": 56000, \n" + " \"married\": true \n" + " }}"; JSONObject jsonObject = new JSONObject(str); String actual = XML.toString(jsonObject, "Test", 2); JSONObject actualJsonObject = XML.toJSONObject(actual); String expected = "\n" + " \n" + " sonoo\n" + " 56000\n" + " true\n" + " \n" + "\n"; JSONObject expectedJsonObject = XML.toJSONObject(expected); assertTrue(expectedJsonObject.similar(actualJsonObject)); } @Test public void testIndentSimpleJsonArray(){ String str = "[ \n" + " {\"name\":\"Ram\", \"email\":\"Ram@gmail.com\"}, \n" + " {\"name\":\"Bob\", \"email\":\"bob32@gmail.com\"} \n" + "] "; JSONArray jsonObject = new JSONArray(str); String actual = XML.toString(jsonObject, 2); JSONObject actualJsonObject = XML.toJSONObject(actual); String expected = "\n" + " Ram\n" + " Ram@gmail.com\n" + "\n" + "\n" + " Bob\n" + " bob32@gmail.com\n" + "\n"; JSONObject expectedJsonObject = XML.toJSONObject(expected); assertTrue(expectedJsonObject.similar(actualJsonObject)); } @Test public void testIndentComplicatedJsonObjectWithArrayAndWithConfig(){ try (InputStream jsonStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue593.json")) { final JSONObject object = new JSONObject(new JSONTokener(jsonStream)); String actualString = XML.toString(object, null, XMLParserConfiguration.KEEP_STRINGS, 2); try (InputStream xmlStream = XMLTest.class.getClassLoader().getResourceAsStream("Issue593.xml")) { int bufferSize = 1024; char[] buffer = new char[bufferSize]; StringBuilder expected = new StringBuilder(); Reader in = new InputStreamReader(xmlStream, "UTF-8"); for (int numRead; (numRead = in.read(buffer, 0, buffer.length)) > 0; ) { expected.append(buffer, 0, numRead); } assertTrue(XML.toJSONObject(expected.toString()).similar(XML.toJSONObject(actualString))); } } catch (IOException e) { fail("file writer error: " +e.getMessage()); } } @Test public void testMaxNestingDepthOf42IsRespected() { final String wayTooLongMalformedXML = new String(new char[6000]).replace("\0", ""); final int maxNestingDepth = 42; try { XML.toJSONObject(wayTooLongMalformedXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth)); fail("Expecting a JSONException"); } catch (JSONException e) { assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">", e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth)); } } @Test public void testMaxNestingDepthIsRespectedWithValidXML() { final String perfectlyFineXML = "\n" + " \n" + " sonoo\n" + " 56000\n" + " true\n" + " \n" + "\n"; final int maxNestingDepth = 1; try { XML.toJSONObject(perfectlyFineXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth)); fail("Expecting a JSONException"); } catch (JSONException e) { assertTrue("Wrong throwable thrown: not expecting message <" + e.getMessage() + ">", e.getMessage().startsWith("Maximum nesting depth of " + maxNestingDepth)); } } @Test public void testMaxNestingDepthWithValidFittingXML() { final String perfectlyFineXML = "\n" + " \n" + " sonoo\n" + " 56000\n" + " true\n" + " \n" + "\n"; final int maxNestingDepth = 3; try { XML.toJSONObject(perfectlyFineXML, XMLParserConfiguration.ORIGINAL.withMaxNestingDepth(maxNestingDepth)); } catch (JSONException e) { e.printStackTrace(); fail("XML document should be parsed as its maximum depth fits the maxNestingDepth " + "parameter of the XMLParserConfiguration used"); } } @Test public void testWithWhitespaceTrimmingDisabled() { String originalXml = " Test Whitespace String \t "; JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false)); String expectedJsonString = "{\"testXml\":\" Test Whitespace String \t \"}"; JSONObject expectedJson = new JSONObject(expectedJsonString); Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); } @Test public void testNestedWithWhitespaceTrimmingDisabled() { String originalXml = "\n"+ "\n"+ "
\n"+ " Sherlock Holmes \n"+ "
\n"+ "
"; JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false)); String expectedJsonString = "{\"addresses\":{\"address\":{\"name\":\" Sherlock Holmes \"}}}"; JSONObject expectedJson = new JSONObject(expectedJsonString); Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); } @Test public void shouldTrimWhitespaceDoesNotSupportTagsEqualingCDataTagName() { // When using withShouldTrimWhitespace = true, input containing tags with same name as cDataTagName is unsupported and should not be used in conjunction String originalXml = "\n"+ "\n"+ "
\n"+ " Sherlock Holmes \n"+ "
\n"+ "
"; JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(false).withcDataTagName("content")); String expectedJsonString = "{\"addresses\":{\"address\":[[\"\\n \",\" Sherlock Holmes \",\"\\n \"]]}}"; JSONObject expectedJson = new JSONObject(expectedJsonString); Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); } @Test public void shouldTrimWhitespaceEnabledDropsTagsEqualingCDataTagNameButValueRemains() { String originalXml = "\n"+ "\n"+ "
\n"+ " Sherlock Holmes \n"+ "
\n"+ "
"; JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(true).withcDataTagName("content")); String expectedJsonString = "{\"addresses\":{\"address\":\"Sherlock Holmes\"}}"; JSONObject expectedJson = new JSONObject(expectedJsonString); Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); } @Test public void testWithWhitespaceTrimmingEnabled() { String originalXml = " Test Whitespace String \t "; JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withShouldTrimWhitespace(true)); String expectedJsonString = "{\"testXml\":\"Test Whitespace String\"}"; JSONObject expectedJson = new JSONObject(expectedJsonString); Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); } @Test public void testWithWhitespaceTrimmingEnabledByDefault() { String originalXml = " Test Whitespace String \t "; JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration()); String expectedJsonString = "{\"testXml\":\"Test Whitespace String\"}"; JSONObject expectedJson = new JSONObject(expectedJsonString); Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson); } @Test public void clarifyCurrentBehavior() { // Behavior documented in #826 // After reverting the code, amount is stored as numeric, and phone is stored as string String str1 = " \n" + " 0123456789\n" + " 0.1230\n" + " true\n" + " "; JSONObject jsonObject1 = XML.toJSONObject(str1, new XMLParserConfiguration().withKeepStrings(false)); assertEquals(jsonObject1.getJSONObject("datatypes").getFloat("amount"), 0.123, .1); assertEquals(jsonObject1.getJSONObject("datatypes").getString("telephone"), "0123456789"); // Behavior documented in #852 // After reverting the code, value is still stored as a number. This is due to how XML.isDecimalNotation() works // and is probably a bug. JSONObject has a similar problem. String str2 = " primary 008E97 "; JSONObject jsonObject2 = XML.toJSONObject(str2); assertEquals(jsonObject2.getJSONObject("color").getLong("value"), 0e897, .1); // Workaround for now is to use keepStrings JSONObject jsonObject3 = XML.toJSONObject(str2, new XMLParserConfiguration().withKeepStrings(true)); assertEquals(jsonObject3.getJSONObject("color").getString("value"), "008E97"); } }