diff --git a/src/main/java/org/json/XML.java b/src/main/java/org/json/XML.java index b7f0c8b2c..9b2ba8939 100644 --- a/src/main/java/org/json/XML.java +++ b/src/main/java/org/json/XML.java @@ -380,12 +380,23 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP if (x.nextToken() != GT) { throw x.syntaxError("Misshaped tag"); } - if (nilAttributeFound) { - context.accumulate(tagName, JSONObject.NULL); - } else if (jsonObject.length() > 0) { - context.accumulate(tagName, jsonObject); + if (config.getForceList().contains(tagName)) { + // Force the value to be an array + if (nilAttributeFound) { + context.append(tagName, JSONObject.NULL); + } else if (jsonObject.length() > 0) { + context.append(tagName, jsonObject); + } else { + context.put(tagName, new JSONArray()); + } } else { - context.accumulate(tagName, ""); + if (nilAttributeFound) { + context.accumulate(tagName, JSONObject.NULL); + } else if (jsonObject.length() > 0) { + context.accumulate(tagName, jsonObject); + } else { + context.accumulate(tagName, ""); + } } return false; @@ -413,14 +424,27 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP } else if (token == LT) { // Nested element if (parse(x, jsonObject, tagName, config)) { - if (jsonObject.length() == 0) { - context.accumulate(tagName, ""); - } else if (jsonObject.length() == 1 - && jsonObject.opt(config.getcDataTagName()) != null) { - context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); + if (config.getForceList().contains(tagName)) { + // Force the value to be an array + if (jsonObject.length() == 0) { + context.put(tagName, new JSONArray()); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.append(tagName, jsonObject.opt(config.getcDataTagName())); + } else { + context.append(tagName, jsonObject); + } } else { - context.accumulate(tagName, jsonObject); + if (jsonObject.length() == 0) { + context.accumulate(tagName, ""); + } else if (jsonObject.length() == 1 + && jsonObject.opt(config.getcDataTagName()) != null) { + context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); + } else { + context.accumulate(tagName, jsonObject); + } } + return false; } } diff --git a/src/main/java/org/json/XMLParserConfiguration.java b/src/main/java/org/json/XMLParserConfiguration.java index af3093bde..a1fd63eb7 100644 --- a/src/main/java/org/json/XMLParserConfiguration.java +++ b/src/main/java/org/json/XMLParserConfiguration.java @@ -25,7 +25,9 @@ of this software and associated documentation files (the "Software"), to deal import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; /** @@ -66,6 +68,12 @@ public class XMLParserConfiguration { */ private Map> xsiTypeMap; + /** + * When parsing the XML into JSON, specifies the tags whose values should be converted + * to arrays + */ + private Set forceList; + /** * Default parser configuration. Does not keep strings (tries to implicitly convert * values), and the CDATA Tag Name is "content". @@ -75,6 +83,7 @@ public XMLParserConfiguration () { this.cDataTagName = "content"; this.convertNilAttributeToNull = false; this.xsiTypeMap = Collections.emptyMap(); + this.forceList = Collections.emptySet(); } /** @@ -151,13 +160,15 @@ public XMLParserConfiguration (final boolean keepStrings, final String cDataTagN * false to parse values with attribute xsi:nil="true" as {"xsi:nil":true}. * @param xsiTypeMap new HashMap>() to parse values with attribute * xsi:type="integer" as integer, xsi:type="string" as string + * @param forceList new HashSet() to parse the provided tags' values as arrays */ private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, - final boolean convertNilAttributeToNull, final Map> xsiTypeMap ) { + final boolean convertNilAttributeToNull, final Map> xsiTypeMap, final Set forceList ) { this.keepStrings = keepStrings; this.cDataTagName = cDataTagName; this.convertNilAttributeToNull = convertNilAttributeToNull; this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap); + this.forceList = Collections.unmodifiableSet(forceList); } /** @@ -174,7 +185,8 @@ protected XMLParserConfiguration clone() { this.keepStrings, this.cDataTagName, this.convertNilAttributeToNull, - this.xsiTypeMap + this.xsiTypeMap, + this.forceList ); } @@ -283,4 +295,26 @@ public XMLParserConfiguration withXsiTypeMap(final Map} to parse the provided tags' values as arrays + * @return forceList unmodifiable configuration set. + */ + public Set getForceList() { + return this.forceList; + } + + /** + * When parsing the XML into JSON, specifies that tags that will be converted to arrays + * in this configuration {@code Set} to parse the provided tags' values as arrays + * @param forceList {@code new HashSet()} to parse the provided tags' values as arrays + * @return The existing configuration will not be modified. A new configuration is returned. + */ + public XMLParserConfiguration withForceList(final Set forceList) { + XMLParserConfiguration newConfig = this.clone(); + Set cloneForceList = new HashSet(forceList); + newConfig.forceList = Collections.unmodifiableSet(cloneForceList); + return newConfig; + } } diff --git a/src/test/java/org/json/junit/XMLConfigurationTest.java b/src/test/java/org/json/junit/XMLConfigurationTest.java index 28b20ddfd..2ab06be05 100755 --- a/src/test/java/org/json/junit/XMLConfigurationTest.java +++ b/src/test/java/org/json/junit/XMLConfigurationTest.java @@ -35,6 +35,8 @@ of this software and associated documentation files (the "Software"), to deal import java.io.IOException; import java.io.Reader; import java.io.StringReader; +import java.util.HashSet; +import java.util.Set; import org.json.JSONArray; import org.json.JSONException; @@ -903,7 +905,172 @@ public void testConfig() { Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray); } - + + /** + * Test forceList parameter + */ + @Test + public void testSimpleForceList() { + String xmlStr = + "\n"+ + "\n"+ + "
\n"+ + " Sherlock Holmes\n"+ + "
\n"+ + "
"; + + String expectedStr = + "{\"addresses\":[{\"address\":{\"name\":\"Sherlock Holmes\"}}]}"; + + Set forceList = new HashSet(); + forceList.add("addresses"); + + XMLParserConfiguration config = + new XMLParserConfiguration() + .withForceList(forceList); + JSONObject jsonObject = XML.toJSONObject(xmlStr, config); + JSONObject expetedJsonObject = new JSONObject(expectedStr); + + Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject); + } + @Test + public void testLongForceList() { + String xmlStr = + ""+ + ""+ + "host1"+ + "Linux"+ + ""+ + ""+ + "em0"+ + "10.0.0.1"+ + ""+ + ""+ + ""+ + ""; + + String expectedStr = + "{"+ + "\"servers\": ["+ + "{"+ + "\"server\": {"+ + "\"name\": \"host1\","+ + "\"os\": \"Linux\","+ + "\"interfaces\": ["+ + "{"+ + "\"interface\": {"+ + "\"name\": \"em0\","+ + "\"ip_address\": \"10.0.0.1\""+ + "}}]}}]}"; + + Set forceList = new HashSet(); + forceList.add("servers"); + forceList.add("interfaces"); + + XMLParserConfiguration config = + new XMLParserConfiguration() + .withForceList(forceList); + JSONObject jsonObject = XML.toJSONObject(xmlStr, config); + JSONObject expetedJsonObject = new JSONObject(expectedStr); + + Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject); + } + @Test + public void testMultipleTagForceList() { + String xmlStr = + "\n"+ + "
\n"+ + " Sherlock Holmes\n"+ + " John H. Watson\n"+ + "
\n"+ + "
"; + + String expectedStr = + "{"+ + "\"addresses\":["+ + "{"+ + "\"address\":["+ + "{"+ + "\"name\":["+ + "\"Sherlock Holmes\","+ + "\"John H. Watson\""+ + "]"+ + "}"+ + "]"+ + "}"+ + "]"+ + "}"; + + Set forceList = new HashSet(); + forceList.add("addresses"); + forceList.add("address"); + forceList.add("name"); + + XMLParserConfiguration config = + new XMLParserConfiguration() + .withForceList(forceList); + JSONObject jsonObject = XML.toJSONObject(xmlStr, config); + JSONObject expetedJsonObject = new JSONObject(expectedStr); + + Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject); + } + @Test + public void testEmptyForceList() { + String xmlStr = + ""; + + String expectedStr = + "{\"addresses\":[]}"; + + Set forceList = new HashSet(); + forceList.add("addresses"); + + XMLParserConfiguration config = + new XMLParserConfiguration() + .withForceList(forceList); + JSONObject jsonObject = XML.toJSONObject(xmlStr, config); + JSONObject expetedJsonObject = new JSONObject(expectedStr); + + Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject); + } + @Test + public void testContentForceList() { + String xmlStr = + "Baker Street"; + + String expectedStr = + "{\"addresses\":[\"Baker Street\"]}"; + + Set forceList = new HashSet(); + forceList.add("addresses"); + + XMLParserConfiguration config = + new XMLParserConfiguration() + .withForceList(forceList); + JSONObject jsonObject = XML.toJSONObject(xmlStr, config); + JSONObject expetedJsonObject = new JSONObject(expectedStr); + + Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject); + } + @Test + public void testEmptyTagForceList() { + String xmlStr = + ""; + + String expectedStr = + "{\"addresses\":[]}"; + + Set forceList = new HashSet(); + forceList.add("addresses"); + + XMLParserConfiguration config = + new XMLParserConfiguration() + .withForceList(forceList); + JSONObject jsonObject = XML.toJSONObject(xmlStr, config); + JSONObject expetedJsonObject = new JSONObject(expectedStr); + + Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject); + } /** * Convenience method, given an input string and expected result,