Merge pull request #646 from Zetmas/feature/issue#595

XMLParserConfiguration support for xml to json arrays
This commit is contained in:
Sean Leary 2021-12-03 19:37:00 -06:00 committed by GitHub
commit cff5cc6c74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 239 additions and 14 deletions

View File

@ -380,12 +380,23 @@ public class XML {
if (x.nextToken() != GT) { if (x.nextToken() != GT) {
throw x.syntaxError("Misshaped tag"); throw x.syntaxError("Misshaped tag");
} }
if (nilAttributeFound) { if (config.getForceList().contains(tagName)) {
context.accumulate(tagName, JSONObject.NULL); // Force the value to be an array
} else if (jsonObject.length() > 0) { if (nilAttributeFound) {
context.accumulate(tagName, jsonObject); context.append(tagName, JSONObject.NULL);
} else if (jsonObject.length() > 0) {
context.append(tagName, jsonObject);
} else {
context.put(tagName, new JSONArray());
}
} else { } 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; return false;
@ -413,14 +424,27 @@ public class XML {
} else if (token == LT) { } else if (token == LT) {
// Nested element // Nested element
if (parse(x, jsonObject, tagName, config)) { if (parse(x, jsonObject, tagName, config)) {
if (jsonObject.length() == 0) { if (config.getForceList().contains(tagName)) {
context.accumulate(tagName, ""); // Force the value to be an array
} else if (jsonObject.length() == 1 if (jsonObject.length() == 0) {
&& jsonObject.opt(config.getcDataTagName()) != null) { context.put(tagName, new JSONArray());
context.accumulate(tagName, jsonObject.opt(config.getcDataTagName())); } else if (jsonObject.length() == 1
&& jsonObject.opt(config.getcDataTagName()) != null) {
context.append(tagName, jsonObject.opt(config.getcDataTagName()));
} else {
context.append(tagName, jsonObject);
}
} else { } 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; return false;
} }
} }

View File

@ -25,7 +25,9 @@ SOFTWARE.
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
@ -66,6 +68,12 @@ public class XMLParserConfiguration {
*/ */
private Map<String, XMLXsiTypeConverter<?>> xsiTypeMap; 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 * Default parser configuration. Does not keep strings (tries to implicitly convert
* values), and the CDATA Tag Name is "content". * values), and the CDATA Tag Name is "content".
@ -75,6 +83,7 @@ public class XMLParserConfiguration {
this.cDataTagName = "content"; this.cDataTagName = "content";
this.convertNilAttributeToNull = false; this.convertNilAttributeToNull = false;
this.xsiTypeMap = Collections.emptyMap(); this.xsiTypeMap = Collections.emptyMap();
this.forceList = Collections.emptySet();
} }
/** /**
@ -151,13 +160,15 @@ public class XMLParserConfiguration {
* <code>false</code> to parse values with attribute xsi:nil="true" as {"xsi:nil":true}. * <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 * @param xsiTypeMap <code>new HashMap<String, XMLXsiTypeConverter<?>>()</code> to parse values with attribute
* xsi:type="integer" as integer, xsi:type="string" as string * 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, 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.keepStrings = keepStrings;
this.cDataTagName = cDataTagName; this.cDataTagName = cDataTagName;
this.convertNilAttributeToNull = convertNilAttributeToNull; this.convertNilAttributeToNull = convertNilAttributeToNull;
this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap); this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
this.forceList = Collections.unmodifiableSet(forceList);
} }
/** /**
@ -174,7 +185,8 @@ public class XMLParserConfiguration {
this.keepStrings, this.keepStrings,
this.cDataTagName, this.cDataTagName,
this.convertNilAttributeToNull, this.convertNilAttributeToNull,
this.xsiTypeMap this.xsiTypeMap,
this.forceList
); );
} }
@ -283,4 +295,26 @@ public class XMLParserConfiguration {
newConfig.xsiTypeMap = Collections.unmodifiableMap(cloneXsiTypeMap); newConfig.xsiTypeMap = Collections.unmodifiableMap(cloneXsiTypeMap);
return newConfig; 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;
}
} }

View File

@ -35,6 +35,8 @@ import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.StringReader; import java.io.StringReader;
import java.util.HashSet;
import java.util.Set;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@ -903,7 +905,172 @@ public class XMLConfigurationTest {
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray); 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, * Convenience method, given an input string and expected result,