mirror of
https://github.com/stleary/JSON-java.git
synced 2025-08-03 11:25:30 -04:00
Merge pull request #540 from kumar529/master
Added type conversion support
This commit is contained in:
commit
7abd89b4bc
@ -26,10 +26,12 @@ SOFTWARE.
|
|||||||
|
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This provides static methods to convert an XML text into a JSONObject, and to
|
* This provides static methods to convert an XML text into a JSONObject, and to
|
||||||
* covert a JSONObject into an XML text.
|
* covert a JSONObject into an XML text.
|
||||||
@ -72,6 +74,8 @@ public class XML {
|
|||||||
*/
|
*/
|
||||||
public static final String NULL_ATTR = "xsi:nil";
|
public static final String NULL_ATTR = "xsi:nil";
|
||||||
|
|
||||||
|
public static final String TYPE_ATTR = "xsi:type";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an iterator for navigating Code Points in a string instead of
|
* Creates an iterator for navigating Code Points in a string instead of
|
||||||
* characters. Once Java7 support is dropped, this can be replaced with
|
* characters. Once Java7 support is dropped, this can be replaced with
|
||||||
@ -257,6 +261,7 @@ public class XML {
|
|||||||
String string;
|
String string;
|
||||||
String tagName;
|
String tagName;
|
||||||
Object token;
|
Object token;
|
||||||
|
XMLXsiTypeConverter<?> xmlXsiTypeConverter;
|
||||||
|
|
||||||
// Test for and skip past these forms:
|
// Test for and skip past these forms:
|
||||||
// <!-- ... -->
|
// <!-- ... -->
|
||||||
@ -336,6 +341,7 @@ public class XML {
|
|||||||
token = null;
|
token = null;
|
||||||
jsonObject = new JSONObject();
|
jsonObject = new JSONObject();
|
||||||
boolean nilAttributeFound = false;
|
boolean nilAttributeFound = false;
|
||||||
|
xmlXsiTypeConverter = null;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
token = x.nextToken();
|
token = x.nextToken();
|
||||||
@ -354,6 +360,9 @@ public class XML {
|
|||||||
&& NULL_ATTR.equals(string)
|
&& NULL_ATTR.equals(string)
|
||||||
&& Boolean.parseBoolean((String) token)) {
|
&& Boolean.parseBoolean((String) token)) {
|
||||||
nilAttributeFound = true;
|
nilAttributeFound = true;
|
||||||
|
} else if(config.getXsiTypeMap() != null && !config.getXsiTypeMap().isEmpty()
|
||||||
|
&& TYPE_ATTR.equals(string)) {
|
||||||
|
xmlXsiTypeConverter = config.getXsiTypeMap().get(token);
|
||||||
} else if (!nilAttributeFound) {
|
} else if (!nilAttributeFound) {
|
||||||
jsonObject.accumulate(string,
|
jsonObject.accumulate(string,
|
||||||
config.isKeepStrings()
|
config.isKeepStrings()
|
||||||
@ -392,9 +401,14 @@ public class XML {
|
|||||||
} else if (token instanceof String) {
|
} else if (token instanceof String) {
|
||||||
string = (String) token;
|
string = (String) token;
|
||||||
if (string.length() > 0) {
|
if (string.length() > 0) {
|
||||||
|
if(xmlXsiTypeConverter != null) {
|
||||||
|
jsonObject.accumulate(config.getcDataTagName(),
|
||||||
|
stringToValue(string, xmlXsiTypeConverter));
|
||||||
|
} else {
|
||||||
jsonObject.accumulate(config.getcDataTagName(),
|
jsonObject.accumulate(config.getcDataTagName(),
|
||||||
config.isKeepStrings() ? string : stringToValue(string));
|
config.isKeepStrings() ? string : stringToValue(string));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else if (token == LT) {
|
} else if (token == LT) {
|
||||||
// Nested element
|
// Nested element
|
||||||
@ -418,6 +432,19 @@ public class XML {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method tries to convert the given string value to the target object
|
||||||
|
* @param string String to convert
|
||||||
|
* @param typeConverter value converter to convert string to integer, boolean e.t.c
|
||||||
|
* @return JSON value of this string or the string
|
||||||
|
*/
|
||||||
|
public static Object stringToValue(String string, XMLXsiTypeConverter<?> typeConverter) {
|
||||||
|
if(typeConverter != null) {
|
||||||
|
return typeConverter.convert(string);
|
||||||
|
}
|
||||||
|
return stringToValue(string);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is the same as {@link JSONObject#stringToValue(String)}.
|
* This method is the same as {@link JSONObject#stringToValue(String)}.
|
||||||
*
|
*
|
||||||
|
@ -23,6 +23,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration object for the XML parser. The configuration is immutable.
|
* Configuration object for the XML parser. The configuration is immutable.
|
||||||
* @author AylwardJ
|
* @author AylwardJ
|
||||||
@ -56,6 +61,11 @@ public class XMLParserConfiguration {
|
|||||||
*/
|
*/
|
||||||
private boolean convertNilAttributeToNull;
|
private boolean convertNilAttributeToNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will allow type conversion for values in XML if xsi:type attribute is defined
|
||||||
|
*/
|
||||||
|
private Map<String, XMLXsiTypeConverter<?>> xsiTypeMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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".
|
||||||
@ -64,6 +74,7 @@ public class XMLParserConfiguration {
|
|||||||
this.keepStrings = false;
|
this.keepStrings = false;
|
||||||
this.cDataTagName = "content";
|
this.cDataTagName = "content";
|
||||||
this.convertNilAttributeToNull = false;
|
this.convertNilAttributeToNull = false;
|
||||||
|
this.xsiTypeMap = Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -130,6 +141,25 @@ public class XMLParserConfiguration {
|
|||||||
this.convertNilAttributeToNull = convertNilAttributeToNull;
|
this.convertNilAttributeToNull = convertNilAttributeToNull;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the parser to use custom settings.
|
||||||
|
* @param keepStrings <code>true</code> to parse all values as string.
|
||||||
|
* <code>false</code> to try and convert XML string values into a JSON value.
|
||||||
|
* @param cDataTagName <code>null</code> to disable CDATA processing. Any other value
|
||||||
|
* to use that value as the JSONObject key name to process as CDATA.
|
||||||
|
* @param convertNilAttributeToNull <code>true</code> to parse values with attribute xsi:nil="true" as null.
|
||||||
|
* <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
|
||||||
|
*/
|
||||||
|
private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName,
|
||||||
|
final boolean convertNilAttributeToNull, final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap ) {
|
||||||
|
this.keepStrings = keepStrings;
|
||||||
|
this.cDataTagName = cDataTagName;
|
||||||
|
this.convertNilAttributeToNull = convertNilAttributeToNull;
|
||||||
|
this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a new instance of the same configuration.
|
* Provides a new instance of the same configuration.
|
||||||
*/
|
*/
|
||||||
@ -143,7 +173,8 @@ public class XMLParserConfiguration {
|
|||||||
return new XMLParserConfiguration(
|
return new XMLParserConfiguration(
|
||||||
this.keepStrings,
|
this.keepStrings,
|
||||||
this.cDataTagName,
|
this.cDataTagName,
|
||||||
this.convertNilAttributeToNull
|
this.convertNilAttributeToNull,
|
||||||
|
this.xsiTypeMap
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,4 +256,31 @@ public class XMLParserConfiguration {
|
|||||||
newConfig.convertNilAttributeToNull = newVal;
|
newConfig.convertNilAttributeToNull = newVal;
|
||||||
return newConfig;
|
return newConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When parsing the XML into JSON, specifies that the values with attribute xsi:type
|
||||||
|
* will be converted to target type defined to client in this configuration
|
||||||
|
* {@code Map<String, XMLXsiTypeConverter<?>>} to parse values with attribute
|
||||||
|
* xsi:type="integer" as integer, xsi:type="string" as string
|
||||||
|
* @return {@link #xsiTypeMap} unmodifiable configuration map.
|
||||||
|
*/
|
||||||
|
public Map<String, XMLXsiTypeConverter<?>> getXsiTypeMap() {
|
||||||
|
return this.xsiTypeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When parsing the XML into JSON, specifies that the values with attribute xsi:type
|
||||||
|
* will be converted to target type defined to client in this configuration
|
||||||
|
* {@code Map<String, XMLXsiTypeConverter<?>>} to parse values with attribute
|
||||||
|
* xsi:type="integer" as integer, xsi:type="string" as string
|
||||||
|
* @param xsiTypeMap {@code new HashMap<String, XMLXsiTypeConverter<?>>()} to parse values with attribute
|
||||||
|
* xsi:type="integer" as integer, xsi:type="string" as string
|
||||||
|
* @return The existing configuration will not be modified. A new configuration is returned.
|
||||||
|
*/
|
||||||
|
public XMLParserConfiguration withXsiTypeMap(final Map<String, XMLXsiTypeConverter<?>> xsiTypeMap) {
|
||||||
|
XMLParserConfiguration newConfig = this.clone();
|
||||||
|
Map<String, XMLXsiTypeConverter<?>> cloneXsiTypeMap = new HashMap<String, XMLXsiTypeConverter<?>>(xsiTypeMap);
|
||||||
|
newConfig.xsiTypeMap = Collections.unmodifiableMap(cloneXsiTypeMap);
|
||||||
|
return newConfig;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
66
src/main/java/org/json/XMLXsiTypeConverter.java
Normal file
66
src/main/java/org/json/XMLXsiTypeConverter.java
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package org.json;
|
||||||
|
/*
|
||||||
|
Copyright (c) 2002 JSON.org
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
The Software shall be used for Good, not Evil.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type conversion configuration interface to be used with xsi:type attributes.
|
||||||
|
* <pre>
|
||||||
|
* <b>XML Sample</b>
|
||||||
|
* {@code
|
||||||
|
* <root>
|
||||||
|
* <asString xsi:type="string">12345</asString>
|
||||||
|
* <asInt xsi:type="integer">54321</asInt>
|
||||||
|
* </root>
|
||||||
|
* }
|
||||||
|
* <b>JSON Output</b>
|
||||||
|
* {@code
|
||||||
|
* {
|
||||||
|
* "root" : {
|
||||||
|
* "asString" : "12345",
|
||||||
|
* "asInt": 54321
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* <b>Usage</b>
|
||||||
|
* {@code
|
||||||
|
* Map<String, XMLXsiTypeConverter<?>> xsiTypeMap = new HashMap<String, XMLXsiTypeConverter<?>>();
|
||||||
|
* xsiTypeMap.put("string", new XMLXsiTypeConverter<String>() {
|
||||||
|
* @Override public String convert(final String value) {
|
||||||
|
* return value;
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* xsiTypeMap.put("integer", new XMLXsiTypeConverter<Integer>() {
|
||||||
|
* @Override public Integer convert(final String value) {
|
||||||
|
* return Integer.valueOf(value);
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
* @author kumar529
|
||||||
|
* @param <T> return type of convert method
|
||||||
|
*/
|
||||||
|
public interface XMLXsiTypeConverter<T> {
|
||||||
|
T convert(String value);
|
||||||
|
}
|
@ -38,6 +38,8 @@ import java.io.InputStream;
|
|||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
@ -45,6 +47,7 @@ import org.json.JSONObject;
|
|||||||
import org.json.JSONTokener;
|
import org.json.JSONTokener;
|
||||||
import org.json.XML;
|
import org.json.XML;
|
||||||
import org.json.XMLParserConfiguration;
|
import org.json.XMLParserConfiguration;
|
||||||
|
import org.json.XMLXsiTypeConverter;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
@ -973,4 +976,96 @@ public class XMLTest {
|
|||||||
assertEquals("Case insensitive Entity unescape", expectedStr, actualStr);
|
assertEquals("Case insensitive Entity unescape", expectedStr, actualStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test passes when xsi:type="java.lang.String" not converting to string
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testToJsonWithTypeWhenTypeConversionDisabled() {
|
||||||
|
String originalXml = "<root><id xsi:type=\"string\">1234</id></root>";
|
||||||
|
String expectedJsonString = "{\"root\":{\"id\":{\"xsi:type\":\"string\",\"content\":1234}}}";
|
||||||
|
JSONObject expectedJson = new JSONObject(expectedJsonString);
|
||||||
|
JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration());
|
||||||
|
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test passes when xsi:type="java.lang.String" converting to String
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testToJsonWithTypeWhenTypeConversionEnabled() {
|
||||||
|
String originalXml = "<root><id1 xsi:type=\"string\">1234</id1>"
|
||||||
|
+ "<id2 xsi:type=\"integer\">1234</id2></root>";
|
||||||
|
String expectedJsonString = "{\"root\":{\"id2\":1234,\"id1\":\"1234\"}}";
|
||||||
|
JSONObject expectedJson = new JSONObject(expectedJsonString);
|
||||||
|
Map<String, XMLXsiTypeConverter<?>> xsiTypeMap = new HashMap<String, XMLXsiTypeConverter<?>>();
|
||||||
|
xsiTypeMap.put("string", new XMLXsiTypeConverter<String>() {
|
||||||
|
@Override public String convert(final String value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
xsiTypeMap.put("integer", new XMLXsiTypeConverter<Integer>() {
|
||||||
|
@Override public Integer convert(final String value) {
|
||||||
|
return Integer.valueOf(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap));
|
||||||
|
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToJsonWithXSITypeWhenTypeConversionEnabled() {
|
||||||
|
String originalXml = "<root><asString xsi:type=\"string\">12345</asString><asInt "
|
||||||
|
+ "xsi:type=\"integer\">54321</asInt></root>";
|
||||||
|
String expectedJsonString = "{\"root\":{\"asString\":\"12345\",\"asInt\":54321}}";
|
||||||
|
JSONObject expectedJson = new JSONObject(expectedJsonString);
|
||||||
|
Map<String, XMLXsiTypeConverter<?>> xsiTypeMap = new HashMap<String, XMLXsiTypeConverter<?>>();
|
||||||
|
xsiTypeMap.put("string", new XMLXsiTypeConverter<String>() {
|
||||||
|
@Override public String convert(final String value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
xsiTypeMap.put("integer", new XMLXsiTypeConverter<Integer>() {
|
||||||
|
@Override public Integer convert(final String value) {
|
||||||
|
return Integer.valueOf(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap));
|
||||||
|
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToJsonWithXSITypeWhenTypeConversionNotEnabledOnOne() {
|
||||||
|
String originalXml = "<root><asString xsi:type=\"string\">12345</asString><asInt>54321</asInt></root>";
|
||||||
|
String expectedJsonString = "{\"root\":{\"asString\":\"12345\",\"asInt\":54321}}";
|
||||||
|
JSONObject expectedJson = new JSONObject(expectedJsonString);
|
||||||
|
Map<String, XMLXsiTypeConverter<?>> xsiTypeMap = new HashMap<String, XMLXsiTypeConverter<?>>();
|
||||||
|
xsiTypeMap.put("string", new XMLXsiTypeConverter<String>() {
|
||||||
|
@Override public String convert(final String value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
JSONObject actualJson = XML.toJSONObject(originalXml, new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap));
|
||||||
|
Util.compareActualVsExpectedJsonObjects(actualJson,expectedJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testXSITypeMapNotModifiable() {
|
||||||
|
Map<String, XMLXsiTypeConverter<?>> xsiTypeMap = new HashMap<String, XMLXsiTypeConverter<?>>();
|
||||||
|
XMLParserConfiguration config = new XMLParserConfiguration().withXsiTypeMap(xsiTypeMap);
|
||||||
|
xsiTypeMap.put("string", new XMLXsiTypeConverter<String>() {
|
||||||
|
@Override public String convert(final String value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertEquals("Config Conversion Map size is expected to be 0", 0, config.getXsiTypeMap().size());
|
||||||
|
|
||||||
|
try {
|
||||||
|
config.getXsiTypeMap().put("boolean", new XMLXsiTypeConverter<Boolean>() {
|
||||||
|
@Override public Boolean convert(final String value) {
|
||||||
|
return Boolean.valueOf(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fail("Expected to be unable to modify the config");
|
||||||
|
} catch (Exception ignored) { }
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user