Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XMLParserConfiguration defined json arrays option #646

Merged
merged 6 commits into from Dec 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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