Skip to content

Commit

Permalink
Merge pull request #646 from Zetmas/feature/issue#595
Browse files Browse the repository at this point in the history
XMLParserConfiguration support for xml to json arrays
  • Loading branch information
stleary committed Dec 4, 2021
2 parents 04e8ea8 + 5dd78bc commit cff5cc6
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 14 deletions.
46 changes: 35 additions & 11 deletions src/main/java/org/json/XML.java
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
}
Expand Down
38 changes: 36 additions & 2 deletions src/main/java/org/json/XMLParserConfiguration.java
Expand Up @@ -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;


/**
Expand Down Expand Up @@ -66,6 +68,12 @@ public class XMLParserConfiguration {
*/
private Map<String, XMLXsiTypeConverter<?>> xsiTypeMap;

/**
* When parsing the XML into JSON, specifies the tags whose values should be converted
* to arrays
*/
private Set<String> forceList;

/**
* Default parser configuration. Does not keep strings (tries to implicitly convert
* values), and the CDATA Tag Name is "content".
Expand All @@ -75,6 +83,7 @@ public XMLParserConfiguration () {
this.cDataTagName = "content";
this.convertNilAttributeToNull = false;
this.xsiTypeMap = Collections.emptyMap();
this.forceList = Collections.emptySet();
}

/**
Expand Down Expand Up @@ -151,13 +160,15 @@ public XMLParserConfiguration (final boolean keepStrings, final String cDataTagN
* <code>false</code> to parse values with attribute xsi:nil="true" as {"xsi:nil":true}.
* @param xsiTypeMap <code>new HashMap<String, XMLXsiTypeConverter<?>>()</code> to parse values with attribute
* xsi:type="integer" as integer, xsi:type="string" as string
* @param forceList <code>new HashSet<String>()</code> to parse the provided tags' values as arrays
*/
private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName,
final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap ) {
final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap, final Set<String> forceList ) {
this.keepStrings = keepStrings;
this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull;
this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
this.forceList = Collections.unmodifiableSet(forceList);
}

/**
Expand All @@ -174,7 +185,8 @@ protected XMLParserConfiguration clone() {
this.keepStrings,
this.cDataTagName,
this.convertNilAttributeToNull,
this.xsiTypeMap
this.xsiTypeMap,
this.forceList
);
}

Expand Down Expand Up @@ -283,4 +295,26 @@ public XMLParserConfiguration withXsiTypeMap(final Map<String, XMLXsiTypeConvert
newConfig.xsiTypeMap = Collections.unmodifiableMap(cloneXsiTypeMap);
return newConfig;
}

/**
* When parsing the XML into JSON, specifies that tags that will be converted to arrays
* in this configuration {@code Set<String>} to parse the provided tags' values as arrays
* @return <code>forceList</code> unmodifiable configuration set.
*/
public Set<String> getForceList() {
return this.forceList;
}

/**
* When parsing the XML into JSON, specifies that tags that will be converted to arrays
* in this configuration {@code Set<String>} to parse the provided tags' values as arrays
* @param forceList {@code new HashSet<String>()} 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<String> forceList) {
XMLParserConfiguration newConfig = this.clone();
Set<String> cloneForceList = new HashSet<String>(forceList);
newConfig.forceList = Collections.unmodifiableSet(cloneForceList);
return newConfig;
}
}
169 changes: 168 additions & 1 deletion src/test/java/org/json/junit/XMLConfigurationTest.java
Expand Up @@ -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;
Expand Down Expand Up @@ -903,7 +905,172 @@ public void testConfig() {
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);

}


/**
* Test forceList parameter
*/
@Test
public void testSimpleForceList() {
String xmlStr =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
"<addresses>\n"+
" <address>\n"+
" <name>Sherlock Holmes</name>\n"+
" </address>\n"+
"</addresses>";

String expectedStr =
"{\"addresses\":[{\"address\":{\"name\":\"Sherlock Holmes\"}}]}";

Set<String> forceList = new HashSet<String>();
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 =
"<servers>"+
"<server>"+
"<name>host1</name>"+
"<os>Linux</os>"+
"<interfaces>"+
"<interface>"+
"<name>em0</name>"+
"<ip_address>10.0.0.1</ip_address>"+
"</interface>"+
"</interfaces>"+
"</server>"+
"</servers>";

String expectedStr =
"{"+
"\"servers\": ["+
"{"+
"\"server\": {"+
"\"name\": \"host1\","+
"\"os\": \"Linux\","+
"\"interfaces\": ["+
"{"+
"\"interface\": {"+
"\"name\": \"em0\","+
"\"ip_address\": \"10.0.0.1\""+
"}}]}}]}";

Set<String> forceList = new HashSet<String>();
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 =
"<addresses>\n"+
" <address>\n"+
" <name>Sherlock Holmes</name>\n"+
" <name>John H. Watson</name>\n"+
" </address>\n"+
"</addresses>";

String expectedStr =
"{"+
"\"addresses\":["+
"{"+
"\"address\":["+
"{"+
"\"name\":["+
"\"Sherlock Holmes\","+
"\"John H. Watson\""+
"]"+
"}"+
"]"+
"}"+
"]"+
"}";

Set<String> forceList = new HashSet<String>();
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 =
"<addresses></addresses>";

String expectedStr =
"{\"addresses\":[]}";

Set<String> forceList = new HashSet<String>();
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 =
"<addresses>Baker Street</addresses>";

String expectedStr =
"{\"addresses\":[\"Baker Street\"]}";

Set<String> forceList = new HashSet<String>();
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 =
"<addresses />";

String expectedStr =
"{\"addresses\":[]}";

Set<String> forceList = new HashSet<String>();
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,
Expand Down

0 comments on commit cff5cc6

Please sign in to comment.