Skip to content

Commit cff5cc6

Browse files
authored
Merge pull request #646 from Zetmas/feature/issue#595
XMLParserConfiguration support for xml to json arrays
2 parents 04e8ea8 + 5dd78bc commit cff5cc6

File tree

3 files changed

+239
-14
lines changed

3 files changed

+239
-14
lines changed

src/main/java/org/json/XML.java

+35-11
Original file line numberDiff line numberDiff line change
@@ -380,12 +380,23 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
380380
if (x.nextToken() != GT) {
381381
throw x.syntaxError("Misshaped tag");
382382
}
383-
if (nilAttributeFound) {
384-
context.accumulate(tagName, JSONObject.NULL);
385-
} else if (jsonObject.length() > 0) {
386-
context.accumulate(tagName, jsonObject);
383+
if (config.getForceList().contains(tagName)) {
384+
// Force the value to be an array
385+
if (nilAttributeFound) {
386+
context.append(tagName, JSONObject.NULL);
387+
} else if (jsonObject.length() > 0) {
388+
context.append(tagName, jsonObject);
389+
} else {
390+
context.put(tagName, new JSONArray());
391+
}
387392
} else {
388-
context.accumulate(tagName, "");
393+
if (nilAttributeFound) {
394+
context.accumulate(tagName, JSONObject.NULL);
395+
} else if (jsonObject.length() > 0) {
396+
context.accumulate(tagName, jsonObject);
397+
} else {
398+
context.accumulate(tagName, "");
399+
}
389400
}
390401
return false;
391402

@@ -413,14 +424,27 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLP
413424
} else if (token == LT) {
414425
// Nested element
415426
if (parse(x, jsonObject, tagName, config)) {
416-
if (jsonObject.length() == 0) {
417-
context.accumulate(tagName, "");
418-
} else if (jsonObject.length() == 1
419-
&& jsonObject.opt(config.getcDataTagName()) != null) {
420-
context.accumulate(tagName, jsonObject.opt(config.getcDataTagName()));
427+
if (config.getForceList().contains(tagName)) {
428+
// Force the value to be an array
429+
if (jsonObject.length() == 0) {
430+
context.put(tagName, new JSONArray());
431+
} else if (jsonObject.length() == 1
432+
&& jsonObject.opt(config.getcDataTagName()) != null) {
433+
context.append(tagName, jsonObject.opt(config.getcDataTagName()));
434+
} else {
435+
context.append(tagName, jsonObject);
436+
}
421437
} else {
422-
context.accumulate(tagName, jsonObject);
438+
if (jsonObject.length() == 0) {
439+
context.accumulate(tagName, "");
440+
} else if (jsonObject.length() == 1
441+
&& jsonObject.opt(config.getcDataTagName()) != null) {
442+
context.accumulate(tagName, jsonObject.opt(config.getcDataTagName()));
443+
} else {
444+
context.accumulate(tagName, jsonObject);
445+
}
423446
}
447+
424448
return false;
425449
}
426450
}

src/main/java/org/json/XMLParserConfiguration.java

+36-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ of this software and associated documentation files (the "Software"), to deal
2525

2626
import java.util.Collections;
2727
import java.util.HashMap;
28+
import java.util.HashSet;
2829
import java.util.Map;
30+
import java.util.Set;
2931

3032

3133
/**
@@ -66,6 +68,12 @@ public class XMLParserConfiguration {
6668
*/
6769
private Map<String, XMLXsiTypeConverter<?>> xsiTypeMap;
6870

71+
/**
72+
* When parsing the XML into JSON, specifies the tags whose values should be converted
73+
* to arrays
74+
*/
75+
private Set<String> forceList;
76+
6977
/**
7078
* Default parser configuration. Does not keep strings (tries to implicitly convert
7179
* values), and the CDATA Tag Name is "content".
@@ -75,6 +83,7 @@ public XMLParserConfiguration () {
7583
this.cDataTagName = "content";
7684
this.convertNilAttributeToNull = false;
7785
this.xsiTypeMap = Collections.emptyMap();
86+
this.forceList = Collections.emptySet();
7887
}
7988

8089
/**
@@ -151,13 +160,15 @@ public XMLParserConfiguration (final boolean keepStrings, final String cDataTagN
151160
* <code>false</code> to parse values with attribute xsi:nil="true" as {"xsi:nil":true}.
152161
* @param xsiTypeMap <code>new HashMap<String, XMLXsiTypeConverter<?>>()</code> to parse values with attribute
153162
* xsi:type="integer" as integer, xsi:type="string" as string
163+
* @param forceList <code>new HashSet<String>()</code> to parse the provided tags' values as arrays
154164
*/
155165
private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName,
156-
final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap ) {
166+
final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap, final Set<String> forceList ) {
157167
this.keepStrings = keepStrings;
158168
this.cDataTagName = cDataTagName;
159169
this.convertNilAttributeToNull = convertNilAttributeToNull;
160170
this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
171+
this.forceList = Collections.unmodifiableSet(forceList);
161172
}
162173

163174
/**
@@ -174,7 +185,8 @@ protected XMLParserConfiguration clone() {
174185
this.keepStrings,
175186
this.cDataTagName,
176187
this.convertNilAttributeToNull,
177-
this.xsiTypeMap
188+
this.xsiTypeMap,
189+
this.forceList
178190
);
179191
}
180192

@@ -283,4 +295,26 @@ public XMLParserConfiguration withXsiTypeMap(final Map<String, XMLXsiTypeConvert
283295
newConfig.xsiTypeMap = Collections.unmodifiableMap(cloneXsiTypeMap);
284296
return newConfig;
285297
}
298+
299+
/**
300+
* When parsing the XML into JSON, specifies that tags that will be converted to arrays
301+
* in this configuration {@code Set<String>} to parse the provided tags' values as arrays
302+
* @return <code>forceList</code> unmodifiable configuration set.
303+
*/
304+
public Set<String> getForceList() {
305+
return this.forceList;
306+
}
307+
308+
/**
309+
* When parsing the XML into JSON, specifies that tags that will be converted to arrays
310+
* in this configuration {@code Set<String>} to parse the provided tags' values as arrays
311+
* @param forceList {@code new HashSet<String>()} to parse the provided tags' values as arrays
312+
* @return The existing configuration will not be modified. A new configuration is returned.
313+
*/
314+
public XMLParserConfiguration withForceList(final Set<String> forceList) {
315+
XMLParserConfiguration newConfig = this.clone();
316+
Set<String> cloneForceList = new HashSet<String>(forceList);
317+
newConfig.forceList = Collections.unmodifiableSet(cloneForceList);
318+
return newConfig;
319+
}
286320
}

src/test/java/org/json/junit/XMLConfigurationTest.java

+168-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ of this software and associated documentation files (the "Software"), to deal
3535
import java.io.IOException;
3636
import java.io.Reader;
3737
import java.io.StringReader;
38+
import java.util.HashSet;
39+
import java.util.Set;
3840

3941
import org.json.JSONArray;
4042
import org.json.JSONException;
@@ -903,7 +905,172 @@ public void testConfig() {
903905
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
904906

905907
}
906-
908+
909+
/**
910+
* Test forceList parameter
911+
*/
912+
@Test
913+
public void testSimpleForceList() {
914+
String xmlStr =
915+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+
916+
"<addresses>\n"+
917+
" <address>\n"+
918+
" <name>Sherlock Holmes</name>\n"+
919+
" </address>\n"+
920+
"</addresses>";
921+
922+
String expectedStr =
923+
"{\"addresses\":[{\"address\":{\"name\":\"Sherlock Holmes\"}}]}";
924+
925+
Set<String> forceList = new HashSet<String>();
926+
forceList.add("addresses");
927+
928+
XMLParserConfiguration config =
929+
new XMLParserConfiguration()
930+
.withForceList(forceList);
931+
JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
932+
JSONObject expetedJsonObject = new JSONObject(expectedStr);
933+
934+
Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
935+
}
936+
@Test
937+
public void testLongForceList() {
938+
String xmlStr =
939+
"<servers>"+
940+
"<server>"+
941+
"<name>host1</name>"+
942+
"<os>Linux</os>"+
943+
"<interfaces>"+
944+
"<interface>"+
945+
"<name>em0</name>"+
946+
"<ip_address>10.0.0.1</ip_address>"+
947+
"</interface>"+
948+
"</interfaces>"+
949+
"</server>"+
950+
"</servers>";
951+
952+
String expectedStr =
953+
"{"+
954+
"\"servers\": ["+
955+
"{"+
956+
"\"server\": {"+
957+
"\"name\": \"host1\","+
958+
"\"os\": \"Linux\","+
959+
"\"interfaces\": ["+
960+
"{"+
961+
"\"interface\": {"+
962+
"\"name\": \"em0\","+
963+
"\"ip_address\": \"10.0.0.1\""+
964+
"}}]}}]}";
965+
966+
Set<String> forceList = new HashSet<String>();
967+
forceList.add("servers");
968+
forceList.add("interfaces");
969+
970+
XMLParserConfiguration config =
971+
new XMLParserConfiguration()
972+
.withForceList(forceList);
973+
JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
974+
JSONObject expetedJsonObject = new JSONObject(expectedStr);
975+
976+
Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
977+
}
978+
@Test
979+
public void testMultipleTagForceList() {
980+
String xmlStr =
981+
"<addresses>\n"+
982+
" <address>\n"+
983+
" <name>Sherlock Holmes</name>\n"+
984+
" <name>John H. Watson</name>\n"+
985+
" </address>\n"+
986+
"</addresses>";
987+
988+
String expectedStr =
989+
"{"+
990+
"\"addresses\":["+
991+
"{"+
992+
"\"address\":["+
993+
"{"+
994+
"\"name\":["+
995+
"\"Sherlock Holmes\","+
996+
"\"John H. Watson\""+
997+
"]"+
998+
"}"+
999+
"]"+
1000+
"}"+
1001+
"]"+
1002+
"}";
1003+
1004+
Set<String> forceList = new HashSet<String>();
1005+
forceList.add("addresses");
1006+
forceList.add("address");
1007+
forceList.add("name");
1008+
1009+
XMLParserConfiguration config =
1010+
new XMLParserConfiguration()
1011+
.withForceList(forceList);
1012+
JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
1013+
JSONObject expetedJsonObject = new JSONObject(expectedStr);
1014+
1015+
Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
1016+
}
1017+
@Test
1018+
public void testEmptyForceList() {
1019+
String xmlStr =
1020+
"<addresses></addresses>";
1021+
1022+
String expectedStr =
1023+
"{\"addresses\":[]}";
1024+
1025+
Set<String> forceList = new HashSet<String>();
1026+
forceList.add("addresses");
1027+
1028+
XMLParserConfiguration config =
1029+
new XMLParserConfiguration()
1030+
.withForceList(forceList);
1031+
JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
1032+
JSONObject expetedJsonObject = new JSONObject(expectedStr);
1033+
1034+
Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
1035+
}
1036+
@Test
1037+
public void testContentForceList() {
1038+
String xmlStr =
1039+
"<addresses>Baker Street</addresses>";
1040+
1041+
String expectedStr =
1042+
"{\"addresses\":[\"Baker Street\"]}";
1043+
1044+
Set<String> forceList = new HashSet<String>();
1045+
forceList.add("addresses");
1046+
1047+
XMLParserConfiguration config =
1048+
new XMLParserConfiguration()
1049+
.withForceList(forceList);
1050+
JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
1051+
JSONObject expetedJsonObject = new JSONObject(expectedStr);
1052+
1053+
Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
1054+
}
1055+
@Test
1056+
public void testEmptyTagForceList() {
1057+
String xmlStr =
1058+
"<addresses />";
1059+
1060+
String expectedStr =
1061+
"{\"addresses\":[]}";
1062+
1063+
Set<String> forceList = new HashSet<String>();
1064+
forceList.add("addresses");
1065+
1066+
XMLParserConfiguration config =
1067+
new XMLParserConfiguration()
1068+
.withForceList(forceList);
1069+
JSONObject jsonObject = XML.toJSONObject(xmlStr, config);
1070+
JSONObject expetedJsonObject = new JSONObject(expectedStr);
1071+
1072+
Util.compareActualVsExpectedJsonObjects(jsonObject, expetedJsonObject);
1073+
}
9071074

9081075
/**
9091076
* Convenience method, given an input string and expected result,

0 commit comments

Comments
 (0)