mirror of
https://github.com/stleary/JSON-java.git
synced 2026-01-24 00:03:17 -05:00
- Added implementation for Enum and Map
- Moving the CustomClass to data folder. - Removing JSONBuilder.java - Moving the implementation of JSONBuilder to JSONObject.
This commit is contained in:
@@ -1,170 +0,0 @@
|
||||
package org.json;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* The {@code JSONBuilder} class provides a configurable mechanism for
|
||||
* defining how different Java types are handled during JSON serialization
|
||||
* or deserialization.
|
||||
*
|
||||
* <p>This class maintains two internal mappings:
|
||||
* <ul>
|
||||
* <li>A {@code classMapping} which maps Java classes to functions that convert
|
||||
* an input {@code Object} into an appropriate instance of that class.</li>
|
||||
* <li>A {@code collectionMapping} which maps collection interfaces (like {@code List}, {@code Set}, {@code Map})
|
||||
* to supplier functions that create new instances of concrete implementations (e.g., {@code ArrayList} for {@code List}).</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>The mappings are initialized with default values for common primitive wrapper types
|
||||
* and collection interfaces, but they can be modified at runtime using setter methods.
|
||||
*
|
||||
* <p>This class is useful in custom JSON serialization/deserialization frameworks where
|
||||
* type transformation and collection instantiation logic needs to be flexible and extensible.
|
||||
*/
|
||||
public class JSONBuilder {
|
||||
|
||||
/**
|
||||
* A mapping from Java classes to functions that convert a generic {@code Object}
|
||||
* into an instance of the target class.
|
||||
*
|
||||
* <p>Examples of default mappings:
|
||||
* <ul>
|
||||
* <li>{@code int.class} or {@code Integer.class} -> Converts a {@code Number} to {@code int}</li>
|
||||
* <li>{@code boolean.class} or {@code Boolean.class} -> Identity function</li>
|
||||
* <li>{@code String.class} -> Identity function</li>
|
||||
* </ul>
|
||||
*/
|
||||
private static final Map<Class<?>, TypeConverter<?>> classMapping = new HashMap<Class<?>, TypeConverter<?>>();
|
||||
|
||||
/**
|
||||
* A mapping from collection interface types to suppliers that produce
|
||||
* instances of concrete collection implementations.
|
||||
*
|
||||
*/
|
||||
private static final Map<Class<?>, InstanceCreator<?>> collectionMapping = new HashMap<Class<?>, InstanceCreator<?>>();
|
||||
|
||||
// Static initializer block to populate default mappings
|
||||
static {
|
||||
classMapping.put(int.class, new TypeConverter<Integer>() {
|
||||
public Integer convert(Object input) {
|
||||
return ((Number) input).intValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(Integer.class, new TypeConverter<Integer>() {
|
||||
public Integer convert(Object input) {
|
||||
return ((Number) input).intValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(double.class, new TypeConverter<Double>() {
|
||||
public Double convert(Object input) {
|
||||
return ((Number) input).doubleValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(Double.class, new TypeConverter<Double>() {
|
||||
public Double convert(Object input) {
|
||||
return ((Number) input).doubleValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(float.class, new TypeConverter<Float>() {
|
||||
public Float convert(Object input) {
|
||||
return ((Number) input).floatValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(Float.class, new TypeConverter<Float>() {
|
||||
public Float convert(Object input) {
|
||||
return ((Number) input).floatValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(long.class, new TypeConverter<Long>() {
|
||||
public Long convert(Object input) {
|
||||
return ((Number) input).longValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(Long.class, new TypeConverter<Long>() {
|
||||
public Long convert(Object input) {
|
||||
return ((Number) input).longValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(boolean.class, new TypeConverter<Boolean>() {
|
||||
public Boolean convert(Object input) {
|
||||
return (Boolean) input;
|
||||
}
|
||||
});
|
||||
classMapping.put(Boolean.class, new TypeConverter<Boolean>() {
|
||||
public Boolean convert(Object input) {
|
||||
return (Boolean) input;
|
||||
}
|
||||
});
|
||||
classMapping.put(String.class, new TypeConverter<String>() {
|
||||
public String convert(Object input) {
|
||||
return (String) input;
|
||||
}
|
||||
});
|
||||
|
||||
collectionMapping.put(List.class, new InstanceCreator<List>() {
|
||||
public List create() {
|
||||
return new ArrayList();
|
||||
}
|
||||
});
|
||||
collectionMapping.put(Set.class, new InstanceCreator<Set>() {
|
||||
public Set create() {
|
||||
return new HashSet();
|
||||
}
|
||||
});
|
||||
collectionMapping.put(Map.class, new InstanceCreator<Map>() {
|
||||
public Map create() {
|
||||
return new HashMap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current class-to-function mapping used for type conversions.
|
||||
*
|
||||
* @return a map of classes to functions that convert an {@code Object} to that class
|
||||
*/
|
||||
public Map<Class<?>, TypeConverter<?>> getClassMapping() {
|
||||
return this.classMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current collection-to-supplier mapping used for instantiating collections.
|
||||
*
|
||||
* @return a map of collection interface types to suppliers of concrete implementations
|
||||
*/
|
||||
public Map<Class<?>, InstanceCreator<?>> getCollectionMapping() {
|
||||
return this.collectionMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or updates a type conversion function for a given class.
|
||||
*
|
||||
* <p>This allows users to customize how objects are converted into specific types
|
||||
* during processing (e.g., JSON deserialization).
|
||||
*
|
||||
* @param clazz the target class for which the conversion function is to be set
|
||||
* @param function a function that takes an {@code Object} and returns an instance of {@code clazz}
|
||||
*/
|
||||
public void setClassMapping(Class<?> clazz, TypeConverter<?> function) {
|
||||
classMapping.put(clazz, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or updates a supplier function for instantiating a collection type.
|
||||
*
|
||||
* <p>This allows customization of which concrete implementation is used for
|
||||
* interface types like {@code List}, {@code Set}, or {@code Map}.
|
||||
*
|
||||
* @param clazz the collection interface class (e.g., {@code List.class})
|
||||
* @param function a supplier that creates a new instance of a concrete implementation
|
||||
*/
|
||||
public void setCollectionMapping(Class<?> clazz, InstanceCreator<?> function) {
|
||||
collectionMapping.put(clazz, function);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.GenericArrayType;
|
||||
|
||||
/**
|
||||
* A JSONObject is an unordered collection of name/value pairs. Its external
|
||||
@@ -121,12 +122,6 @@ public class JSONObject {
|
||||
*/
|
||||
static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?");
|
||||
|
||||
|
||||
/**
|
||||
* A Builder class for handling the conversion of JSON to Object.
|
||||
*/
|
||||
private JSONBuilder builder;
|
||||
|
||||
/**
|
||||
* The map where the JSONObject's properties are kept.
|
||||
*/
|
||||
@@ -162,6 +157,145 @@ public class JSONObject {
|
||||
this.map = new HashMap<String, Object>();
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapping from Java classes to functions that convert a generic {@code Object}
|
||||
* into an instance of the target class.
|
||||
*
|
||||
* <p>Examples of default mappings:
|
||||
* <ul>
|
||||
* <li>{@code int.class} or {@code Integer.class} -> Converts a {@code Number} to {@code int}</li>
|
||||
* <li>{@code boolean.class} or {@code Boolean.class} -> Identity function</li>
|
||||
* <li>{@code String.class} -> Identity function</li>
|
||||
* </ul>
|
||||
*/
|
||||
private static final Map<Class<?>, TypeConverter<?>> classMapping = new HashMap<Class<?>, TypeConverter<?>>();
|
||||
|
||||
/**
|
||||
* A mapping from collection interface types to suppliers that produce
|
||||
* instances of concrete collection implementations.
|
||||
*
|
||||
*/
|
||||
private static final Map<Class<?>, InstanceCreator<?>> collectionMapping = new HashMap<Class<?>, InstanceCreator<?>>();
|
||||
|
||||
// Static initializer block to populate default mappings
|
||||
static {
|
||||
classMapping.put(int.class, new TypeConverter<Integer>() {
|
||||
public Integer convert(Object input) {
|
||||
return ((Number) input).intValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(Integer.class, new TypeConverter<Integer>() {
|
||||
public Integer convert(Object input) {
|
||||
return ((Number) input).intValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(double.class, new TypeConverter<Double>() {
|
||||
public Double convert(Object input) {
|
||||
return ((Number) input).doubleValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(Double.class, new TypeConverter<Double>() {
|
||||
public Double convert(Object input) {
|
||||
return ((Number) input).doubleValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(float.class, new TypeConverter<Float>() {
|
||||
public Float convert(Object input) {
|
||||
return ((Number) input).floatValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(Float.class, new TypeConverter<Float>() {
|
||||
public Float convert(Object input) {
|
||||
return ((Number) input).floatValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(long.class, new TypeConverter<Long>() {
|
||||
public Long convert(Object input) {
|
||||
return ((Number) input).longValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(Long.class, new TypeConverter<Long>() {
|
||||
public Long convert(Object input) {
|
||||
return ((Number) input).longValue();
|
||||
}
|
||||
});
|
||||
classMapping.put(boolean.class, new TypeConverter<Boolean>() {
|
||||
public Boolean convert(Object input) {
|
||||
return (Boolean) input;
|
||||
}
|
||||
});
|
||||
classMapping.put(Boolean.class, new TypeConverter<Boolean>() {
|
||||
public Boolean convert(Object input) {
|
||||
return (Boolean) input;
|
||||
}
|
||||
});
|
||||
classMapping.put(String.class, new TypeConverter<String>() {
|
||||
public String convert(Object input) {
|
||||
return (String) input;
|
||||
}
|
||||
});
|
||||
|
||||
collectionMapping.put(List.class, new InstanceCreator<List>() {
|
||||
public List create() {
|
||||
return new ArrayList();
|
||||
}
|
||||
});
|
||||
collectionMapping.put(Set.class, new InstanceCreator<Set>() {
|
||||
public Set create() {
|
||||
return new HashSet();
|
||||
}
|
||||
});
|
||||
collectionMapping.put(Map.class, new InstanceCreator<Map>() {
|
||||
public Map create() {
|
||||
return new HashMap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current class-to-function mapping used for type conversions.
|
||||
*
|
||||
* @return a map of classes to functions that convert an {@code Object} to that class
|
||||
*/
|
||||
public Map<Class<?>, TypeConverter<?>> getClassMapping() {
|
||||
return this.classMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current collection-to-supplier mapping used for instantiating collections.
|
||||
*
|
||||
* @return a map of collection interface types to suppliers of concrete implementations
|
||||
*/
|
||||
public Map<Class<?>, InstanceCreator<?>> getCollectionMapping() {
|
||||
return collectionMapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or updates a type conversion function for a given class.
|
||||
*
|
||||
* <p>This allows users to customize how objects are converted into specific types
|
||||
* during processing (e.g., JSON deserialization).
|
||||
*
|
||||
* @param clazz the target class for which the conversion function is to be set
|
||||
* @param function a function that takes an {@code Object} and returns an instance of {@code clazz}
|
||||
*/
|
||||
public void setClassMapping(Class<?> clazz, TypeConverter<?> function) {
|
||||
classMapping.put(clazz, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or updates a supplier function for instantiating a collection type.
|
||||
*
|
||||
* <p>This allows customization of which concrete implementation is used for
|
||||
* interface types like {@code List}, {@code Set}, or {@code Map}.
|
||||
*
|
||||
* @param clazz the collection interface class (e.g., {@code List.class})
|
||||
* @param function a supplier that creates a new instance of a concrete implementation
|
||||
*/
|
||||
public void setCollectionMapping(Class<?> clazz, InstanceCreator<?> function) {
|
||||
collectionMapping.put(clazz, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a JSONObject from a subset of another JSONObject. An array of
|
||||
* strings is used to identify the keys that should be copied. Missing keys
|
||||
@@ -220,25 +354,6 @@ public class JSONObject {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a JSONObject with JSONBuilder for conversion from JSON to POJO
|
||||
*
|
||||
* @param builder builder option for json to POJO
|
||||
*/
|
||||
public JSONObject(JSONBuilder builder) {
|
||||
this();
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to set JSONBuilder.
|
||||
*
|
||||
* @param builder
|
||||
*/
|
||||
public void setJSONBuilder(JSONBuilder builder) {
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses entirety of JSON object
|
||||
*
|
||||
@@ -3236,114 +3351,59 @@ public class JSONObject {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a JSON string into an instance of the specified class.
|
||||
*
|
||||
* <p>This method attempts to map JSON key-value pairs to the corresponding fields
|
||||
* of the given class. It supports basic data types including int, double, float,
|
||||
* long, and boolean (as well as their boxed counterparts). The class must have a
|
||||
* no-argument constructor, and the field names in the class must match the keys
|
||||
* in the JSON string.
|
||||
*
|
||||
* @param clazz the class of the object to be returned
|
||||
* @param <T> the type of the object
|
||||
* @return an instance of type T with fields populated from the JSON string
|
||||
* Helper method to extract the raw Class from Type.
|
||||
*/
|
||||
public <T> T fromJson(Class<T> clazz) {
|
||||
try {
|
||||
T obj = clazz.getDeclaredConstructor().newInstance();
|
||||
if (this.builder == null) {
|
||||
this.builder = new JSONBuilder();
|
||||
private Class<?> getRawType(Type type) {
|
||||
if (type instanceof Class) {
|
||||
return (Class<?>) type;
|
||||
} else if (type instanceof ParameterizedType) {
|
||||
return (Class<?>) ((ParameterizedType) type).getRawType();
|
||||
} else if (type instanceof GenericArrayType) {
|
||||
return Object[].class; // Simplified handling for arrays
|
||||
}
|
||||
Map<Class<?>, TypeConverter<?>> classMapping = this.builder.getClassMapping();
|
||||
|
||||
for (Field field: clazz.getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
String fieldName = field.getName();
|
||||
if (this.has(fieldName)) {
|
||||
Object value = this.get(fieldName);
|
||||
Class<?> pojoClass = field.getType();
|
||||
if (classMapping.containsKey(pojoClass)) {
|
||||
field.set(obj, classMapping.get(pojoClass).convert(value));
|
||||
} else {
|
||||
if (value.getClass() == JSONObject.class) {
|
||||
field.set(obj, fromJson((JSONObject) value, pojoClass));
|
||||
} else if (value.getClass() == JSONArray.class) {
|
||||
if (Collection.class.isAssignableFrom(pojoClass)) {
|
||||
|
||||
Collection<?> nestedCollection = fromJsonArray((JSONArray) value,
|
||||
(Class<? extends Collection>) pojoClass,
|
||||
field.getGenericType());
|
||||
|
||||
field.set(obj, nestedCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new JSONException(e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new JSONException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new JSONException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new JSONException(e);
|
||||
}
|
||||
return Object.class; // Fallback
|
||||
}
|
||||
|
||||
private <T> Collection<T> fromJsonArray(JSONArray jsonArray, Class<?> collectionType, Type elementType) throws JSONException {
|
||||
try {
|
||||
Map<Class<?>, TypeConverter<?>> classMapping = this.builder.getClassMapping();
|
||||
Map<Class<?>, InstanceCreator<?>> collectionMapping = this.builder.getCollectionMapping();
|
||||
Collection<T> collection = (Collection<T>) (collectionMapping.containsKey(collectionType) ?
|
||||
collectionMapping.get(collectionType).create()
|
||||
: collectionType.getDeclaredConstructor().newInstance());
|
||||
|
||||
|
||||
Class<?> innerElementClass = null;
|
||||
Type innerElementType = null;
|
||||
if (elementType instanceof ParameterizedType) {
|
||||
ParameterizedType pType = (ParameterizedType) elementType;
|
||||
innerElementType = pType.getActualTypeArguments()[0];
|
||||
innerElementClass = (innerElementType instanceof Class) ?
|
||||
(Class<?>) innerElementType
|
||||
: (Class<?>) ((ParameterizedType) innerElementType).getRawType();
|
||||
} else {
|
||||
innerElementClass = (Class<?>) elementType;
|
||||
/**
|
||||
* Extracts the element Type for a Collection Type.
|
||||
*/
|
||||
private Type getElementType(Type type) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
Type[] args = ((ParameterizedType) type).getActualTypeArguments();
|
||||
return args.length > 0 ? args[0] : Object.class;
|
||||
}
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
Object jsonElement = jsonArray.get(i);
|
||||
if (classMapping.containsKey(innerElementClass)) {
|
||||
collection.add((T) classMapping.get(innerElementClass).convert(jsonElement));
|
||||
} else if (jsonElement.getClass() == JSONObject.class) {
|
||||
collection.add((T) ((JSONObject) jsonElement).fromJson(innerElementClass));
|
||||
} else if (jsonElement.getClass() == JSONArray.class) {
|
||||
if (Collection.class.isAssignableFrom(innerElementClass)) {
|
||||
|
||||
Collection<?> nestedCollection = fromJsonArray((JSONArray) jsonElement,
|
||||
innerElementClass,
|
||||
innerElementType);
|
||||
|
||||
collection.add((T) nestedCollection);
|
||||
} else {
|
||||
throw new JSONException("Expected collection type for nested JSONArray, but got: " + innerElementClass);
|
||||
/**
|
||||
* Extracts the key and value Types for a Map Type.
|
||||
*/
|
||||
private Type[] getMapTypes(Type type) {
|
||||
if (type instanceof ParameterizedType) {
|
||||
Type[] args = ((ParameterizedType) type).getActualTypeArguments();
|
||||
if (args.length == 2) {
|
||||
return args;
|
||||
}
|
||||
} else {
|
||||
collection.add((T) jsonElement.toString());
|
||||
}
|
||||
}
|
||||
return collection;
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new JSONException(e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new JSONException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new JSONException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new JSONException(e);
|
||||
}
|
||||
return new Type[]{Object.class, Object.class}; // Default: String keys, Object values
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a JSON string into an instance of the specified class.
|
||||
*
|
||||
* <p>This method attempts to map JSON key-value pairs to the corresponding fields
|
||||
* of the given class. It supports basic data types including int, double, float,
|
||||
* long, and boolean (as well as their boxed counterparts). The class must have a
|
||||
* no-argument constructor, and the field names in the class must match the keys
|
||||
* in the JSON string.
|
||||
*
|
||||
* @param jsonString json in string format
|
||||
* @param clazz the class of the object to be returned
|
||||
* @return an instance of Object T with fields populated from the JSON string
|
||||
*/
|
||||
public static <T> T fromJson(String jsonString, Class<T> clazz) {
|
||||
JSONObject jsonObject = new JSONObject(jsonString);
|
||||
return jsonObject.fromJson(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3355,12 +3415,161 @@ public class JSONObject {
|
||||
* no-argument constructor, and the field names in the class must match the keys
|
||||
* in the JSON string.
|
||||
*
|
||||
* @param object JSONObject of internal class
|
||||
* @param clazz the class of the object to be returned
|
||||
* @param <T> the type of the object
|
||||
* @return an instance of type T with fields populated from the JSON string
|
||||
*/
|
||||
private <T> T fromJson(JSONObject object, Class<T> clazz) {
|
||||
return object.fromJson(clazz);
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T fromJson(Class<T> clazz) {
|
||||
try {
|
||||
T obj = clazz.getDeclaredConstructor().newInstance();
|
||||
for (Field field : clazz.getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
String fieldName = field.getName();
|
||||
if (has(fieldName)) {
|
||||
Object value = get(fieldName);
|
||||
Type fieldType = field.getGenericType();
|
||||
Class<?> rawType = getRawType(fieldType);
|
||||
if (classMapping.containsKey(rawType)) {
|
||||
field.set(obj, classMapping.get(rawType).convert(value));
|
||||
} else {
|
||||
Object convertedValue = convertValue(value, fieldType);
|
||||
field.set(obj, convertedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new JSONException("No no-arg constructor for class: " + clazz.getName(), e);
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new JSONException("Failed to instantiate or set field for class: " + clazz.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles non-primitive types (Enum, Map, JSONObject, JSONArray) during deserialization.
|
||||
* Now dispatches to the recursive convertValue for consistency.
|
||||
*/
|
||||
private <T> void handleNonDataTypes(Class<?> pojoClass, Object value, Field field, T obj) throws JSONException {
|
||||
try {
|
||||
Type fieldType = field.getGenericType();
|
||||
Object convertedValue = convertValue(value, fieldType);
|
||||
field.set(obj, convertedValue);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new JSONException("Failed to set field: " + field.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively converts a value to the target Type, handling nested generics for Collections and Maps.
|
||||
*/
|
||||
private Object convertValue(Object value, Type targetType) throws JSONException {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> rawType = getRawType(targetType);
|
||||
|
||||
// Direct assignment
|
||||
if (rawType.isAssignableFrom(value.getClass())) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Use registered type converter
|
||||
if (classMapping.containsKey(rawType)) {
|
||||
return classMapping.get(rawType).convert(value);
|
||||
}
|
||||
|
||||
// Enum conversion
|
||||
if (rawType.isEnum() && value instanceof String) {
|
||||
return stringToEnum(rawType, (String) value);
|
||||
}
|
||||
|
||||
// Collection handling (e.g., List<List<Map<String, Integer>>>)
|
||||
if (Collection.class.isAssignableFrom(rawType)) {
|
||||
if (value instanceof JSONArray) {
|
||||
Type elementType = getElementType(targetType);
|
||||
return fromJsonArray((JSONArray) value, rawType, elementType);
|
||||
}
|
||||
}
|
||||
// Map handling (e.g., Map<Integer, List<String>>)
|
||||
else if (Map.class.isAssignableFrom(rawType)) {
|
||||
if (value instanceof JSONObject) {
|
||||
Type[] mapTypes = getMapTypes(targetType);
|
||||
Type keyType = mapTypes[0];
|
||||
Type valueType = mapTypes[1];
|
||||
return convertToMap((JSONObject) value, keyType, valueType, rawType);
|
||||
}
|
||||
}
|
||||
// POJO handling (including custom classes like Tuple<Integer, String, Integer>)
|
||||
else if (!rawType.isPrimitive() && !rawType.isEnum()) {
|
||||
if (value instanceof JSONObject) {
|
||||
// Recurse with the raw class for POJO deserialization
|
||||
return ((JSONObject) value).fromJson(rawType);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a JSONObject to a Map with the specified generic key and value Types.
|
||||
* Supports nested types via recursive convertValue.
|
||||
*/
|
||||
private Map<?, ?> convertToMap(JSONObject jsonMap, Type keyType, Type valueType, Class<?> mapType) throws JSONException {
|
||||
try {
|
||||
InstanceCreator<?> creator = collectionMapping.getOrDefault(mapType, () -> new HashMap<>());
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Object, Object> map = (Map<Object, Object>) creator.create();
|
||||
|
||||
for (Object keyObj : jsonMap.keySet()) {
|
||||
String keyStr = (String) keyObj;
|
||||
Object mapValue = jsonMap.get(keyStr);
|
||||
// Convert key (e.g., String to Integer for Map<Integer, ...>)
|
||||
Object convertedKey = convertValue(keyStr, keyType);
|
||||
// Convert value recursively (handles nesting)
|
||||
Object convertedValue = convertValue(mapValue, valueType);
|
||||
map.put(convertedKey, convertedValue);
|
||||
}
|
||||
return map;
|
||||
} catch (Exception e) {
|
||||
throw new JSONException("Failed to convert JSONObject to Map: " + mapType.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a String to an Enum value.
|
||||
*/
|
||||
private <E extends Enum<E>> E stringToEnum(Class<?> enumClass, String value) throws JSONException {
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Method valueOfMethod = enumClass.getMethod("valueOf", String.class);
|
||||
return (E) valueOfMethod.invoke(null, value);
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new JSONException("Failed to convert string to enum: " + value + " for " + enumClass.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a JSONArray into a Collection, supporting nested generics.
|
||||
* Uses recursive convertValue for elements.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Collection<T> fromJsonArray(JSONArray jsonArray, Class<?> collectionType, Type elementType) throws JSONException {
|
||||
try {
|
||||
InstanceCreator<?> creator = collectionMapping.getOrDefault(collectionType, () -> new ArrayList<>());
|
||||
Collection<T> collection = (Collection<T>) creator.create();
|
||||
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
Object jsonElement = jsonArray.get(i);
|
||||
// Recursively convert each element using the full element Type (handles nesting)
|
||||
Object convertedValue = convertValue(jsonElement, elementType);
|
||||
collection.add((T) convertedValue);
|
||||
}
|
||||
return collection;
|
||||
} catch (Exception e) {
|
||||
throw new JSONException("Failed to convert JSONArray to Collection: " + collectionType.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.json.JSONObject;
|
||||
import org.json.JSONPointerException;
|
||||
import org.json.JSONParserConfiguration;
|
||||
import org.json.JSONString;
|
||||
import org.json.JSONBuilder;
|
||||
import org.json.JSONTokener;
|
||||
import org.json.ParserConfiguration;
|
||||
import org.json.XML;
|
||||
@@ -58,6 +57,17 @@ import org.json.junit.data.RecursiveBeanEquals;
|
||||
import org.json.junit.data.Singleton;
|
||||
import org.json.junit.data.SingletonEnum;
|
||||
import org.json.junit.data.WeirdList;
|
||||
import org.json.junit.data.CustomClass;
|
||||
import org.json.junit.data.CustomClassA;
|
||||
import org.json.junit.data.CustomClassB;
|
||||
import org.json.junit.data.CustomClassC;
|
||||
import org.json.junit.data.CustomClassD;
|
||||
import org.json.junit.data.CustomClassE;
|
||||
import org.json.junit.data.CustomClassF;
|
||||
import org.json.junit.data.CustomClassG;
|
||||
import org.json.junit.data.CustomClassH;
|
||||
import org.json.junit.data.CustomClassI;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.After;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
@@ -4110,36 +4120,14 @@ public class JSONObjectTest {
|
||||
assertEquals(customClass, compareClass);
|
||||
}
|
||||
|
||||
public static class CustomClass {
|
||||
public int number;
|
||||
public String name;
|
||||
public Long longNumber;
|
||||
|
||||
public CustomClass() {}
|
||||
public CustomClass (int number, String name, Long longNumber) {
|
||||
this.number = number;
|
||||
this.name = name;
|
||||
this.longNumber = longNumber;
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
CustomClass customClass = (CustomClass) o;
|
||||
|
||||
return (this.number == customClass.number
|
||||
&& this.name.equals(customClass.name)
|
||||
&& this.longNumber.equals(customClass.longNumber));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jsonObjectParseFromJson_1() {
|
||||
JSONBuilder builder = new JSONBuilder();
|
||||
builder.setClassMapping(java.time.LocalDateTime.class, new TypeConverter<java.time.LocalDateTime>() {
|
||||
JSONObject object = new JSONObject();
|
||||
object.setClassMapping(java.time.LocalDateTime.class, new TypeConverter<java.time.LocalDateTime>() {
|
||||
public java.time.LocalDateTime convert(Object input) {
|
||||
return java.time.LocalDateTime.parse((String) input);
|
||||
}
|
||||
});
|
||||
JSONObject object = new JSONObject(builder);
|
||||
java.time.LocalDateTime localDateTime = java.time.LocalDateTime.now();
|
||||
object.put("localDate", localDateTime.toString());
|
||||
CustomClassA customClassA = object.fromJson(CustomClassA.class);
|
||||
@@ -4147,21 +4135,6 @@ public class JSONObjectTest {
|
||||
assertEquals(customClassA, compareClassClassA);
|
||||
}
|
||||
|
||||
public static class CustomClassA {
|
||||
public java.time.LocalDateTime localDate;
|
||||
|
||||
public CustomClassA() {}
|
||||
public CustomClassA(java.time.LocalDateTime localDate) {
|
||||
this.localDate = localDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
CustomClassA classA = (CustomClassA) o;
|
||||
return this.localDate.equals(classA.localDate);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jsonObjectParseFromJson_2() {
|
||||
JSONObject object = new JSONObject();
|
||||
@@ -4179,54 +4152,6 @@ public class JSONObjectTest {
|
||||
assertEquals(customClassB, compareClassB);
|
||||
}
|
||||
|
||||
public static class CustomClassB {
|
||||
public int number;
|
||||
public CustomClassC classC;
|
||||
|
||||
public CustomClassB() {}
|
||||
public CustomClassB(int number, CustomClassC classC) {
|
||||
this.number = number;
|
||||
this.classC = classC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
CustomClassB classB = (CustomClassB) o;
|
||||
return this.number == classB.number
|
||||
&& this.classC.equals(classB.classC);
|
||||
}
|
||||
}
|
||||
|
||||
public static class CustomClassC {
|
||||
public String stringName;
|
||||
public Long longNumber;
|
||||
|
||||
public CustomClassC() {}
|
||||
public CustomClassC(String stringName, Long longNumber) {
|
||||
this.stringName = stringName;
|
||||
this.longNumber = longNumber;
|
||||
}
|
||||
|
||||
public JSONObject toJSON() {
|
||||
JSONObject object = new JSONObject();
|
||||
object.put("stringName", this.stringName);
|
||||
object.put("longNumber", this.longNumber);
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
CustomClassC classC = (CustomClassC) o;
|
||||
return this.stringName.equals(classC.stringName)
|
||||
&& this.longNumber.equals(classC.longNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return java.util.Objects.hash(stringName, longNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jsonObjectParseFromJson_3() {
|
||||
JSONObject object = new JSONObject();
|
||||
@@ -4241,21 +4166,6 @@ public class JSONObjectTest {
|
||||
assertEquals(customClassD, compareClassD);
|
||||
}
|
||||
|
||||
public static class CustomClassD {
|
||||
public List<String> stringList;
|
||||
|
||||
public CustomClassD() {}
|
||||
public CustomClassD(List<String> stringList) {
|
||||
this.stringList = stringList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
CustomClassD classD = (CustomClassD) o;
|
||||
return this.stringList.equals(classD.stringList);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jsonObjectParseFromJson_4() {
|
||||
JSONObject object = new JSONObject();
|
||||
@@ -4271,21 +4181,6 @@ public class JSONObjectTest {
|
||||
assertEquals(customClassE, compareClassE);
|
||||
}
|
||||
|
||||
public static class CustomClassE {
|
||||
public List<CustomClassC> listClassC;
|
||||
|
||||
public CustomClassE() {}
|
||||
public CustomClassE(List<CustomClassC> listClassC) {
|
||||
this.listClassC = listClassC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
CustomClassE classE = (CustomClassE) o;
|
||||
return this.listClassC.equals(classE.listClassC);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jsonObjectParseFromJson_5() {
|
||||
JSONObject object = new JSONObject();
|
||||
@@ -4302,18 +4197,43 @@ public class JSONObjectTest {
|
||||
assertEquals(customClassF, compareClassF);
|
||||
}
|
||||
|
||||
public static class CustomClassF {
|
||||
public List<List<String>> listOfString;
|
||||
@Test
|
||||
public void jsonObjectParseFromJson_6() {
|
||||
JSONObject object = new JSONObject();
|
||||
Map<String, String> dataList = new HashMap<>();
|
||||
dataList.put("A", "Aa");
|
||||
dataList.put("B", "Bb");
|
||||
dataList.put("C", "Cc");
|
||||
object.put("dataList", dataList);
|
||||
|
||||
public CustomClassF() {}
|
||||
public CustomClassF(List<List<String>> listOfString) {
|
||||
this.listOfString = listOfString;
|
||||
}
|
||||
CustomClassG customClassG = object.fromJson(CustomClassG.class);
|
||||
CustomClassG compareClassG = new CustomClassG(dataList);
|
||||
assertEquals(customClassG, compareClassG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
CustomClassF classF = (CustomClassF) o;
|
||||
return this.listOfString.equals(classF.listOfString);
|
||||
}
|
||||
@Test
|
||||
public void jsonObjectParseFromJson_7() {
|
||||
JSONObject object = new JSONObject();
|
||||
Map<String, List<Integer>> dataList = new HashMap<>();
|
||||
dataList.put("1", Arrays.asList(1, 2, 3, 4));
|
||||
dataList.put("2", Arrays.asList(2, 3, 4, 5));
|
||||
object.put("integerMap", dataList);
|
||||
|
||||
CustomClassH customClassH = object.fromJson(CustomClassH.class);
|
||||
CustomClassH compareClassH = new CustomClassH(dataList);
|
||||
assertEquals(customClassH.integerMap.toString(), compareClassH.integerMap.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jsonObjectParseFromJson_8() {
|
||||
JSONObject object = new JSONObject();
|
||||
Map<String, Map<String, Integer>> dataList = new HashMap<>();
|
||||
dataList.put("1", Collections.singletonMap("1", 1));
|
||||
dataList.put("2", Collections.singletonMap("2", 2));
|
||||
object.put("integerMap", dataList);
|
||||
|
||||
CustomClassI customClassI = object.fromJson(CustomClassI.class);
|
||||
CustomClassI compareClassI = new CustomClassI(dataList);
|
||||
assertEquals(customClassI.integerMap.toString(), compareClassI.integerMap.toString());
|
||||
}
|
||||
}
|
||||
|
||||
23
src/test/java/org/json/junit/data/CustomClass.java
Normal file
23
src/test/java/org/json/junit/data/CustomClass.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package org.json.junit.data;
|
||||
|
||||
public class CustomClass {
|
||||
public int number;
|
||||
public String name;
|
||||
public Long longNumber;
|
||||
|
||||
public CustomClass() {}
|
||||
public CustomClass (int number, String name, Long longNumber) {
|
||||
this.number = number;
|
||||
this.name = name;
|
||||
this.longNumber = longNumber;
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
CustomClass customClass = (CustomClass) o;
|
||||
|
||||
return (this.number == customClass.number
|
||||
&& this.name.equals(customClass.name)
|
||||
&& this.longNumber.equals(customClass.longNumber));
|
||||
}
|
||||
}
|
||||
|
||||
17
src/test/java/org/json/junit/data/CustomClassA.java
Normal file
17
src/test/java/org/json/junit/data/CustomClassA.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package org.json.junit.data;
|
||||
|
||||
public class CustomClassA {
|
||||
public java.time.LocalDateTime localDate;
|
||||
|
||||
public CustomClassA() {}
|
||||
public CustomClassA(java.time.LocalDateTime localDate) {
|
||||
this.localDate = localDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
CustomClassA classA = (CustomClassA) o;
|
||||
return this.localDate.equals(classA.localDate);
|
||||
}
|
||||
}
|
||||
|
||||
20
src/test/java/org/json/junit/data/CustomClassB.java
Normal file
20
src/test/java/org/json/junit/data/CustomClassB.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package org.json.junit.data;
|
||||
|
||||
public class CustomClassB {
|
||||
public int number;
|
||||
public CustomClassC classC;
|
||||
|
||||
public CustomClassB() {}
|
||||
public CustomClassB(int number, CustomClassC classC) {
|
||||
this.number = number;
|
||||
this.classC = classC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
CustomClassB classB = (CustomClassB) o;
|
||||
return this.number == classB.number
|
||||
&& this.classC.equals(classB.classC);
|
||||
}
|
||||
}
|
||||
|
||||
34
src/test/java/org/json/junit/data/CustomClassC.java
Normal file
34
src/test/java/org/json/junit/data/CustomClassC.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package org.json.junit.data;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class CustomClassC {
|
||||
public String stringName;
|
||||
public Long longNumber;
|
||||
|
||||
public CustomClassC() {}
|
||||
public CustomClassC(String stringName, Long longNumber) {
|
||||
this.stringName = stringName;
|
||||
this.longNumber = longNumber;
|
||||
}
|
||||
|
||||
public JSONObject toJSON() {
|
||||
JSONObject object = new JSONObject();
|
||||
object.put("stringName", this.stringName);
|
||||
object.put("longNumber", this.longNumber);
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
CustomClassC classC = (CustomClassC) o;
|
||||
return this.stringName.equals(classC.stringName)
|
||||
&& this.longNumber.equals(classC.longNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return java.util.Objects.hash(stringName, longNumber);
|
||||
}
|
||||
}
|
||||
|
||||
19
src/test/java/org/json/junit/data/CustomClassD.java
Normal file
19
src/test/java/org/json/junit/data/CustomClassD.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package org.json.junit.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CustomClassD {
|
||||
public List<String> stringList;
|
||||
|
||||
public CustomClassD() {}
|
||||
public CustomClassD(List<String> stringList) {
|
||||
this.stringList = stringList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
CustomClassD classD = (CustomClassD) o;
|
||||
return this.stringList.equals(classD.stringList);
|
||||
}
|
||||
}
|
||||
|
||||
18
src/test/java/org/json/junit/data/CustomClassE.java
Normal file
18
src/test/java/org/json/junit/data/CustomClassE.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package org.json.junit.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CustomClassE {
|
||||
public List<CustomClassC> listClassC;
|
||||
|
||||
public CustomClassE() {}
|
||||
public CustomClassE(List<CustomClassC> listClassC) {
|
||||
this.listClassC = listClassC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
CustomClassE classE = (CustomClassE) o;
|
||||
return this.listClassC.equals(classE.listClassC);
|
||||
}
|
||||
}
|
||||
19
src/test/java/org/json/junit/data/CustomClassF.java
Normal file
19
src/test/java/org/json/junit/data/CustomClassF.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package org.json.junit.data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CustomClassF {
|
||||
public List<List<String>> listOfString;
|
||||
|
||||
public CustomClassF() {}
|
||||
public CustomClassF(List<List<String>> listOfString) {
|
||||
this.listOfString = listOfString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
CustomClassF classF = (CustomClassF) o;
|
||||
return this.listOfString.equals(classF.listOfString);
|
||||
}
|
||||
}
|
||||
|
||||
18
src/test/java/org/json/junit/data/CustomClassG.java
Normal file
18
src/test/java/org/json/junit/data/CustomClassG.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package org.json.junit.data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class CustomClassG {
|
||||
public Map<String, String> dataList;
|
||||
|
||||
public CustomClassG () {}
|
||||
public CustomClassG (Map<String, String> dataList) {
|
||||
this.dataList = dataList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
CustomClassG classG = (CustomClassG) object;
|
||||
return this.dataList.equals(classG.dataList);
|
||||
}
|
||||
}
|
||||
22
src/test/java/org/json/junit/data/CustomClassH.java
Normal file
22
src/test/java/org/json/junit/data/CustomClassH.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package org.json.junit.data;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class CustomClassH {
|
||||
public Map<String, List<Integer>> integerMap;
|
||||
|
||||
public CustomClassH() {}
|
||||
public CustomClassH(Map<String, List<Integer>> integerMap) {
|
||||
this.integerMap = integerMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
CustomClassH classH = (CustomClassH) object;
|
||||
return this.integerMap.size() == classH.integerMap.size()
|
||||
&& this.integerMap.keySet().equals(classH.integerMap.keySet())
|
||||
&& new ArrayList<>(this.integerMap.values()).equals(new ArrayList<>(classH.integerMap.values()));
|
||||
}
|
||||
}
|
||||
12
src/test/java/org/json/junit/data/CustomClassI.java
Normal file
12
src/test/java/org/json/junit/data/CustomClassI.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package org.json.junit.data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class CustomClassI {
|
||||
public Map<String, Map<String, Integer>> integerMap;
|
||||
|
||||
public CustomClassI() {}
|
||||
public CustomClassI(Map<String, Map<String, Integer>> integerMap) {
|
||||
this.integerMap = integerMap;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user