From 83a0e34be5bb572276873bdfd3f5b31da5bc4a48 Mon Sep 17 00:00:00 2001 From: sk02241994 Date: Tue, 9 Sep 2025 15:05:34 +1000 Subject: [PATCH 01/12] 1003: Implement JSONObject.fromJson() with unit tests --- src/main/java/org/json/JSONBuilder.java | 122 ++++++++++ src/main/java/org/json/JSONObject.java | 146 ++++++++++++ .../java/org/json/junit/JSONObjectTest.java | 216 ++++++++++++++++++ 3 files changed, 484 insertions(+) create mode 100644 src/main/java/org/json/JSONBuilder.java diff --git a/src/main/java/org/json/JSONBuilder.java b/src/main/java/org/json/JSONBuilder.java new file mode 100644 index 000000000..2ee99ca58 --- /dev/null +++ b/src/main/java/org/json/JSONBuilder.java @@ -0,0 +1,122 @@ +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; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * The {@code JSONBuilder} class provides a configurable mechanism for + * defining how different Java types are handled during JSON serialization + * or deserialization. + * + *

This class maintains two internal mappings: + *

+ * + *

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. + * + *

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. + * + *

Examples of default mappings: + *

+ */ + private static final Map, Function> classMapping = new HashMap<>(); + + /** + * A mapping from collection interface types to suppliers that produce + * instances of concrete collection implementations. + * + *

Examples of default mappings: + *

    + *
  • {@code List.class} -> {@code ArrayList::new}
  • + *
  • {@code Set.class} -> {@code HashSet::new}
  • + *
  • {@code Map.class} -> {@code HashMap::new}
  • + *
+ */ + private static final Map, Supplier> collectionMapping = new HashMap<>(); + + // Static initializer block to populate default mappings + static { + classMapping.put(int.class, s -> ((Number) s).intValue()); + classMapping.put(Integer.class, s -> ((Number) s).intValue()); + classMapping.put(double.class, s -> ((Number) s).doubleValue()); + classMapping.put(Double.class, s -> ((Number) s).doubleValue()); + classMapping.put(float.class, s -> ((Number) s).floatValue()); + classMapping.put(Float.class, s -> ((Number) s).floatValue()); + classMapping.put(long.class, s -> ((Number) s).longValue()); + classMapping.put(Long.class, s -> ((Number) s).longValue()); + classMapping.put(boolean.class, s -> s); + classMapping.put(Boolean.class, s -> s); + classMapping.put(String.class, s -> s); + + collectionMapping.put(List.class, ArrayList::new); + collectionMapping.put(Set.class, HashSet::new); + collectionMapping.put(Map.class, HashMap::new); + } + + /** + * 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, Function> 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, Supplier> getCollectionMapping() { + return this.collectionMapping; + } + + /** + * Adds or updates a type conversion function for a given class. + * + *

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, Function function) { + classMapping.put(clazz, function); + } + + /** + * Adds or updates a supplier function for instantiating a collection type. + * + *

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, Supplier function) { + collectionMapping.put(clazz, function); + } +} diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 257eb1074..496a15af6 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -17,6 +17,10 @@ import java.util.*; import java.util.Map.Entry; import java.util.regex.Pattern; +import java.util.function.Function; +import java.util.function.Supplier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; /** * A JSONObject is an unordered collection of name/value pairs. Its external @@ -119,6 +123,12 @@ public String toString() { */ 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. */ @@ -212,6 +222,25 @@ public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration } } + /** + * 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 * @@ -3207,4 +3236,121 @@ private static JSONException recursivelyDefinedObjectException(String key) { "JavaBean object contains recursively defined member variable of key " + quote(key) ); } + + /** + * Deserializes a JSON string into an instance of the specified class. + * + *

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 the type of the object + * @return an instance of type T with fields populated from the JSON string + */ + public T fromJson(Class clazz) { + try { + T obj = clazz.getDeclaredConstructor().newInstance(); + if (this.builder == null) { + this.builder = new JSONBuilder(); + } + Map, Function> 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).apply(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) pojoClass, + field.getGenericType()); + + field.set(obj, nestedCollection); + } + } + } + } + } + return obj; + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new JSONException(e); + } + } + + private Collection fromJsonArray(JSONArray jsonArray, Class collectionType, Type elementType) throws JSONException { + try { + Map, Function> classMapping = this.builder.getClassMapping(); + Map, Supplier> collectionMapping = this.builder.getCollectionMapping(); + Collection collection = (Collection) (collectionMapping.containsKey(collectionType) ? + collectionMapping.get(collectionType).get() + : 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; + } + + for (int i = 0; i < jsonArray.length(); i++) { + Object jsonElement = jsonArray.get(i); + if (classMapping.containsKey(innerElementClass)) { + collection.add((T) classMapping.get(innerElementClass).apply(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); + } + } else { + collection.add((T) jsonElement.toString()); + } + } + return collection; + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new JSONException(e); + } + } + + /** + * Deserializes a JSON string into an instance of the specified class. + * + *

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 object JSONObject of internal class + * @param clazz the class of the object to be returned + * @param the type of the object + * @return an instance of type T with fields populated from the JSON string + */ + private T fromJson(JSONObject object, Class clazz) { + return object.fromJson(clazz); + } } diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index 88c19c7dc..e3fb1d813 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -33,6 +33,7 @@ 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; @@ -4095,4 +4096,219 @@ public void jsonObjectParseNullFieldsWithoutParserConfiguration() { assertTrue("JSONObject should be empty", jsonObject.isEmpty()); } + + @Test + public void jsonObjectParseFromJson_0() { + JSONObject object = new JSONObject(); + object.put("number", 12); + object.put("name", "Alex"); + object.put("longNumber", 1500000000L); + String jsonObject = object.toString(); + CustomClass customClass = object.fromJson(CustomClass.class); + CustomClass compareClass = new CustomClass(12, "Alex", 1500000000L); + 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, s -> java.time.LocalDateTime.parse((String)s)); + JSONObject object = new JSONObject(builder); + java.time.LocalDateTime localDateTime = java.time.LocalDateTime.now(); + object.put("localDate", localDateTime.toString()); + CustomClassA customClassA = object.fromJson(CustomClassA.class); + CustomClassA compareClassClassA = new CustomClassA(localDateTime); + 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(); + object.put("number", 12); + + JSONObject classC = new JSONObject(); + classC.put("stringName", "Alex"); + classC.put("longNumber", 123456L); + + object.put("classC", classC); + + CustomClassB customClassB = object.fromJson(CustomClassB.class); + CustomClassC classCObject = new CustomClassC("Alex", 123456L); + CustomClassB compareClassB = new CustomClassB(12, classCObject); + 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(); + JSONArray array = new JSONArray(); + array.put("test1"); + array.put("test2"); + array.put("test3"); + object.put("stringList", array); + + CustomClassD customClassD = object.fromJson(CustomClassD.class); + CustomClassD compareClassD = new CustomClassD(Arrays.asList("test1", "test2", "test3")); + assertEquals(customClassD, compareClassD); + } + + public static class CustomClassD { + public List stringList; + + public CustomClassD() {} + public CustomClassD(List 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(); + JSONArray array = new JSONArray(); + array.put(new CustomClassC("test1", 1L).toJSON()); + array.put(new CustomClassC("test2", 2L).toJSON()); + object.put("listClassC", array); + + CustomClassE customClassE = object.fromJson(CustomClassE.class); + CustomClassE compareClassE = new CustomClassE(java.util.Arrays.asList( + new CustomClassC("test1", 1L), + new CustomClassC("test2", 2L))); + assertEquals(customClassE, compareClassE); + } + + public static class CustomClassE { + public List listClassC; + + public CustomClassE() {} + public CustomClassE(List 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(); + JSONArray array = new JSONArray(); + array.put(Arrays.asList("A", "B", "C")); + array.put(Arrays.asList("D", "E")); + object.put("listOfString", array); + + CustomClassF customClassF = object.fromJson(CustomClassF.class); + List> listOfString = new ArrayList<>(); + listOfString.add(Arrays.asList("A", "B", "C")); + listOfString.add(Arrays.asList("D", "E")); + CustomClassF compareClassF = new CustomClassF(listOfString); + assertEquals(customClassF, compareClassF); + } + + public static class CustomClassF { + public List> listOfString; + + public CustomClassF() {} + public CustomClassF(List> listOfString) { + this.listOfString = listOfString; + } + + @Override + public boolean equals(Object o) { + CustomClassF classF = (CustomClassF) o; + return this.listOfString.equals(classF.listOfString); + } + } } From 7d28955216c9dde9e4617a0abb9b95def69680a0 Mon Sep 17 00:00:00 2001 From: sk02241994 Date: Tue, 9 Sep 2025 16:51:52 +1000 Subject: [PATCH 02/12] Updating to work with java 1.6 --- src/main/java/org/json/InstanceCreator.java | 16 +++ src/main/java/org/json/JSONBuilder.java | 104 +++++++++++++----- src/main/java/org/json/JSONObject.java | 12 +- src/main/java/org/json/TypeConverter.java | 18 +++ .../java/org/json/junit/JSONObjectTest.java | 7 +- 5 files changed, 122 insertions(+), 35 deletions(-) create mode 100644 src/main/java/org/json/InstanceCreator.java create mode 100644 src/main/java/org/json/TypeConverter.java diff --git a/src/main/java/org/json/InstanceCreator.java b/src/main/java/org/json/InstanceCreator.java new file mode 100644 index 000000000..4836e23da --- /dev/null +++ b/src/main/java/org/json/InstanceCreator.java @@ -0,0 +1,16 @@ +package org.json; + +/** + * Interface defining a creator that produces new instances of type {@code T}. + * + * @param the type of instances created + */ +public interface InstanceCreator { + + /** + * Creates a new instance of type {@code T}. + * + * @return a new instance of {@code T} + */ + T create(); +} diff --git a/src/main/java/org/json/JSONBuilder.java b/src/main/java/org/json/JSONBuilder.java index 2ee99ca58..67c9b9418 100644 --- a/src/main/java/org/json/JSONBuilder.java +++ b/src/main/java/org/json/JSONBuilder.java @@ -7,8 +7,6 @@ import java.util.Set; import java.util.HashSet; import java.util.Collection; -import java.util.function.Function; -import java.util.function.Supplier; /** * The {@code JSONBuilder} class provides a configurable mechanism for @@ -42,38 +40,88 @@ public class JSONBuilder { *

  • {@code String.class} -> Identity function
  • * */ - private static final Map, Function> classMapping = new HashMap<>(); + private static final Map, TypeConverter> classMapping = new HashMap<>(); /** * A mapping from collection interface types to suppliers that produce * instances of concrete collection implementations. * - *

    Examples of default mappings: - *

      - *
    • {@code List.class} -> {@code ArrayList::new}
    • - *
    • {@code Set.class} -> {@code HashSet::new}
    • - *
    • {@code Map.class} -> {@code HashMap::new}
    • - *
    */ - private static final Map, Supplier> collectionMapping = new HashMap<>(); + private static final Map, InstanceCreator> collectionMapping = new HashMap<>(); // Static initializer block to populate default mappings static { - classMapping.put(int.class, s -> ((Number) s).intValue()); - classMapping.put(Integer.class, s -> ((Number) s).intValue()); - classMapping.put(double.class, s -> ((Number) s).doubleValue()); - classMapping.put(Double.class, s -> ((Number) s).doubleValue()); - classMapping.put(float.class, s -> ((Number) s).floatValue()); - classMapping.put(Float.class, s -> ((Number) s).floatValue()); - classMapping.put(long.class, s -> ((Number) s).longValue()); - classMapping.put(Long.class, s -> ((Number) s).longValue()); - classMapping.put(boolean.class, s -> s); - classMapping.put(Boolean.class, s -> s); - classMapping.put(String.class, s -> s); + classMapping.put(int.class, new TypeConverter() { + public Integer convert(Object input) { + return ((Number) input).intValue(); + } + }); + classMapping.put(Integer.class, new TypeConverter() { + public Integer convert(Object input) { + return ((Number) input).intValue(); + } + }); + classMapping.put(double.class, new TypeConverter() { + public Double convert(Object input) { + return ((Number) input).doubleValue(); + } + }); + classMapping.put(Double.class, new TypeConverter() { + public Double convert(Object input) { + return ((Number) input).doubleValue(); + } + }); + classMapping.put(float.class, new TypeConverter() { + public Float convert(Object input) { + return ((Number) input).floatValue(); + } + }); + classMapping.put(Float.class, new TypeConverter() { + public Float convert(Object input) { + return ((Number) input).floatValue(); + } + }); + classMapping.put(long.class, new TypeConverter() { + public Long convert(Object input) { + return ((Number) input).longValue(); + } + }); + classMapping.put(Long.class, new TypeConverter() { + public Long convert(Object input) { + return ((Number) input).longValue(); + } + }); + classMapping.put(boolean.class, new TypeConverter() { + public Boolean convert(Object input) { + return (Boolean) input; + } + }); + classMapping.put(Boolean.class, new TypeConverter() { + public Boolean convert(Object input) { + return (Boolean) input; + } + }); + classMapping.put(String.class, new TypeConverter() { + public String convert(Object input) { + return (String) input; + } + }); - collectionMapping.put(List.class, ArrayList::new); - collectionMapping.put(Set.class, HashSet::new); - collectionMapping.put(Map.class, HashMap::new); + collectionMapping.put(List.class, new InstanceCreator() { + public List create() { + return new ArrayList(); + } + }); + collectionMapping.put(Set.class, new InstanceCreator() { + public Set create() { + return new HashSet(); + } + }); + collectionMapping.put(Map.class, new InstanceCreator() { + public Map create() { + return new HashMap(); + } + }); } /** @@ -81,7 +129,7 @@ public class JSONBuilder { * * @return a map of classes to functions that convert an {@code Object} to that class */ - public Map, Function> getClassMapping() { + public Map, TypeConverter> getClassMapping() { return this.classMapping; } @@ -90,7 +138,7 @@ public class JSONBuilder { * * @return a map of collection interface types to suppliers of concrete implementations */ - public Map, Supplier> getCollectionMapping() { + public Map, InstanceCreator> getCollectionMapping() { return this.collectionMapping; } @@ -103,7 +151,7 @@ public Map, Supplier> getCollectionMapping() { * @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, Function function) { + public void setClassMapping(Class clazz, TypeConverter function) { classMapping.put(clazz, function); } @@ -116,7 +164,7 @@ public void setClassMapping(Class clazz, Function function) { * @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, Supplier function) { + public void setCollectionMapping(Class clazz, InstanceCreator function) { collectionMapping.put(clazz, function); } } diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 496a15af6..f5d2bd656 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -3256,7 +3256,7 @@ public T fromJson(Class clazz) { if (this.builder == null) { this.builder = new JSONBuilder(); } - Map, Function> classMapping = this.builder.getClassMapping(); + Map, TypeConverter> classMapping = this.builder.getClassMapping(); for (Field field: clazz.getDeclaredFields()) { field.setAccessible(true); @@ -3265,7 +3265,7 @@ public T fromJson(Class clazz) { Object value = this.get(fieldName); Class pojoClass = field.getType(); if (classMapping.containsKey(pojoClass)) { - field.set(obj, classMapping.get(pojoClass).apply(value)); + field.set(obj, classMapping.get(pojoClass).convert(value)); } else { if (value.getClass() == JSONObject.class) { field.set(obj, fromJson((JSONObject) value, pojoClass)); @@ -3290,10 +3290,10 @@ public T fromJson(Class clazz) { private Collection fromJsonArray(JSONArray jsonArray, Class collectionType, Type elementType) throws JSONException { try { - Map, Function> classMapping = this.builder.getClassMapping(); - Map, Supplier> collectionMapping = this.builder.getCollectionMapping(); + Map, TypeConverter> classMapping = this.builder.getClassMapping(); + Map, InstanceCreator> collectionMapping = this.builder.getCollectionMapping(); Collection collection = (Collection) (collectionMapping.containsKey(collectionType) ? - collectionMapping.get(collectionType).get() + collectionMapping.get(collectionType).create() : collectionType.getDeclaredConstructor().newInstance()); @@ -3312,7 +3312,7 @@ private Collection fromJsonArray(JSONArray jsonArray, Class collection for (int i = 0; i < jsonArray.length(); i++) { Object jsonElement = jsonArray.get(i); if (classMapping.containsKey(innerElementClass)) { - collection.add((T) classMapping.get(innerElementClass).apply(jsonElement)); + 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) { diff --git a/src/main/java/org/json/TypeConverter.java b/src/main/java/org/json/TypeConverter.java new file mode 100644 index 000000000..dc07325e3 --- /dev/null +++ b/src/main/java/org/json/TypeConverter.java @@ -0,0 +1,18 @@ +package org.json; + +/** + * Interface defining a converter that converts an input {@code Object} + * into an instance of a specific type {@code T}. + * + * @param the target type to convert to + */ +public interface TypeConverter { + + /** + * Converts the given input object to an instance of type {@code T}. + * + * @param input the object to convert + * @return the converted instance of type {@code T} + */ + T convert(Object input); +} diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index e3fb1d813..5a7aedb7c 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -37,6 +37,7 @@ import org.json.JSONTokener; import org.json.ParserConfiguration; import org.json.XML; +import org.json.TypeConverter; import org.json.junit.data.BrokenToString; import org.json.junit.data.ExceptionalBean; import org.json.junit.data.Fraction; @@ -4133,7 +4134,11 @@ public boolean equals(Object o) { @Test public void jsonObjectParseFromJson_1() { JSONBuilder builder = new JSONBuilder(); - builder.setClassMapping(java.time.LocalDateTime.class, s -> java.time.LocalDateTime.parse((String)s)); + builder.setClassMapping(java.time.LocalDateTime.class, new TypeConverter() { + 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()); From ebc13d66853323ca439749560b5f883f2ca6b583 Mon Sep 17 00:00:00 2001 From: sk02241994 Date: Tue, 9 Sep 2025 17:01:30 +1000 Subject: [PATCH 03/12] Updating to work with java 1.6 --- src/main/java/org/json/JSONBuilder.java | 4 ++-- src/main/java/org/json/JSONObject.java | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/json/JSONBuilder.java b/src/main/java/org/json/JSONBuilder.java index 67c9b9418..36f558049 100644 --- a/src/main/java/org/json/JSONBuilder.java +++ b/src/main/java/org/json/JSONBuilder.java @@ -40,14 +40,14 @@ public class JSONBuilder { *
  • {@code String.class} -> Identity function
  • * */ - private static final Map, TypeConverter> classMapping = new HashMap<>(); + private static final Map, TypeConverter> classMapping = new HashMap, TypeConverter>(); /** * A mapping from collection interface types to suppliers that produce * instances of concrete collection implementations. * */ - private static final Map, InstanceCreator> collectionMapping = new HashMap<>(); + private static final Map, InstanceCreator> collectionMapping = new HashMap, InstanceCreator>(); // Static initializer block to populate default mappings static { diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index f5d2bd656..db4ec981c 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -3283,7 +3283,13 @@ public T fromJson(Class clazz) { } } return obj; - } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + } 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); } } @@ -3331,7 +3337,13 @@ private Collection fromJsonArray(JSONArray jsonArray, Class collection } } return collection; - } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + } 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); } } From fbb6b3158eb186189a1b35e9902f24d0ad8cddbc Mon Sep 17 00:00:00 2001 From: sk02241994 Date: Tue, 9 Sep 2025 17:02:24 +1000 Subject: [PATCH 04/12] Updating to work with java 1.6 --- src/main/java/org/json/JSONObject.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index db4ec981c..f6e1d43ce 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -17,8 +17,6 @@ import java.util.*; import java.util.Map.Entry; import java.util.regex.Pattern; -import java.util.function.Function; -import java.util.function.Supplier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; From 0521928463bbb65c4ca9c4921131469c28ec5308 Mon Sep 17 00:00:00 2001 From: sk02241994 Date: Sun, 28 Sep 2025 19:26:09 +1000 Subject: [PATCH 05/12] - Added implementation for Enum and Map - Moving the CustomClass to data folder. - Removing JSONBuilder.java - Moving the implementation of JSONBuilder to JSONObject. --- src/main/java/org/json/JSONBuilder.java | 170 ------- src/main/java/org/json/JSONObject.java | 461 +++++++++++++----- .../java/org/json/junit/JSONObjectTest.java | 178 ++----- .../java/org/json/junit/data/CustomClass.java | 23 + .../org/json/junit/data/CustomClassA.java | 17 + .../org/json/junit/data/CustomClassB.java | 20 + .../org/json/junit/data/CustomClassC.java | 34 ++ .../org/json/junit/data/CustomClassD.java | 19 + .../org/json/junit/data/CustomClassE.java | 18 + .../org/json/junit/data/CustomClassF.java | 19 + .../org/json/junit/data/CustomClassG.java | 18 + .../org/json/junit/data/CustomClassH.java | 22 + .../org/json/junit/data/CustomClassI.java | 12 + 13 files changed, 586 insertions(+), 425 deletions(-) delete mode 100644 src/main/java/org/json/JSONBuilder.java create mode 100644 src/test/java/org/json/junit/data/CustomClass.java create mode 100644 src/test/java/org/json/junit/data/CustomClassA.java create mode 100644 src/test/java/org/json/junit/data/CustomClassB.java create mode 100644 src/test/java/org/json/junit/data/CustomClassC.java create mode 100644 src/test/java/org/json/junit/data/CustomClassD.java create mode 100644 src/test/java/org/json/junit/data/CustomClassE.java create mode 100644 src/test/java/org/json/junit/data/CustomClassF.java create mode 100644 src/test/java/org/json/junit/data/CustomClassG.java create mode 100644 src/test/java/org/json/junit/data/CustomClassH.java create mode 100644 src/test/java/org/json/junit/data/CustomClassI.java diff --git a/src/main/java/org/json/JSONBuilder.java b/src/main/java/org/json/JSONBuilder.java deleted file mode 100644 index 36f558049..000000000 --- a/src/main/java/org/json/JSONBuilder.java +++ /dev/null @@ -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. - * - *

    This class maintains two internal mappings: - *

      - *
    • A {@code classMapping} which maps Java classes to functions that convert - * an input {@code Object} into an appropriate instance of that class.
    • - *
    • 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}).
    • - *
    - * - *

    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. - * - *

    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. - * - *

    Examples of default mappings: - *

      - *
    • {@code int.class} or {@code Integer.class} -> Converts a {@code Number} to {@code int}
    • - *
    • {@code boolean.class} or {@code Boolean.class} -> Identity function
    • - *
    • {@code String.class} -> Identity function
    • - *
    - */ - private static final Map, TypeConverter> classMapping = new HashMap, TypeConverter>(); - - /** - * A mapping from collection interface types to suppliers that produce - * instances of concrete collection implementations. - * - */ - private static final Map, InstanceCreator> collectionMapping = new HashMap, InstanceCreator>(); - - // Static initializer block to populate default mappings - static { - classMapping.put(int.class, new TypeConverter() { - public Integer convert(Object input) { - return ((Number) input).intValue(); - } - }); - classMapping.put(Integer.class, new TypeConverter() { - public Integer convert(Object input) { - return ((Number) input).intValue(); - } - }); - classMapping.put(double.class, new TypeConverter() { - public Double convert(Object input) { - return ((Number) input).doubleValue(); - } - }); - classMapping.put(Double.class, new TypeConverter() { - public Double convert(Object input) { - return ((Number) input).doubleValue(); - } - }); - classMapping.put(float.class, new TypeConverter() { - public Float convert(Object input) { - return ((Number) input).floatValue(); - } - }); - classMapping.put(Float.class, new TypeConverter() { - public Float convert(Object input) { - return ((Number) input).floatValue(); - } - }); - classMapping.put(long.class, new TypeConverter() { - public Long convert(Object input) { - return ((Number) input).longValue(); - } - }); - classMapping.put(Long.class, new TypeConverter() { - public Long convert(Object input) { - return ((Number) input).longValue(); - } - }); - classMapping.put(boolean.class, new TypeConverter() { - public Boolean convert(Object input) { - return (Boolean) input; - } - }); - classMapping.put(Boolean.class, new TypeConverter() { - public Boolean convert(Object input) { - return (Boolean) input; - } - }); - classMapping.put(String.class, new TypeConverter() { - public String convert(Object input) { - return (String) input; - } - }); - - collectionMapping.put(List.class, new InstanceCreator() { - public List create() { - return new ArrayList(); - } - }); - collectionMapping.put(Set.class, new InstanceCreator() { - public Set create() { - return new HashSet(); - } - }); - collectionMapping.put(Map.class, new InstanceCreator() { - 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, 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, InstanceCreator> getCollectionMapping() { - return this.collectionMapping; - } - - /** - * Adds or updates a type conversion function for a given class. - * - *

    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. - * - *

    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); - } -} diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index f6e1d43ce..52bd2fedc 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -19,6 +19,7 @@ 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 String toString() { */ 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 JSONObject() { this.map = new HashMap(); } + /** + * A mapping from Java classes to functions that convert a generic {@code Object} + * into an instance of the target class. + * + *

    Examples of default mappings: + *

      + *
    • {@code int.class} or {@code Integer.class} -> Converts a {@code Number} to {@code int}
    • + *
    • {@code boolean.class} or {@code Boolean.class} -> Identity function
    • + *
    • {@code String.class} -> Identity function
    • + *
    + */ + private static final Map, TypeConverter> classMapping = new HashMap, TypeConverter>(); + + /** + * A mapping from collection interface types to suppliers that produce + * instances of concrete collection implementations. + * + */ + private static final Map, InstanceCreator> collectionMapping = new HashMap, InstanceCreator>(); + + // Static initializer block to populate default mappings + static { + classMapping.put(int.class, new TypeConverter() { + public Integer convert(Object input) { + return ((Number) input).intValue(); + } + }); + classMapping.put(Integer.class, new TypeConverter() { + public Integer convert(Object input) { + return ((Number) input).intValue(); + } + }); + classMapping.put(double.class, new TypeConverter() { + public Double convert(Object input) { + return ((Number) input).doubleValue(); + } + }); + classMapping.put(Double.class, new TypeConverter() { + public Double convert(Object input) { + return ((Number) input).doubleValue(); + } + }); + classMapping.put(float.class, new TypeConverter() { + public Float convert(Object input) { + return ((Number) input).floatValue(); + } + }); + classMapping.put(Float.class, new TypeConverter() { + public Float convert(Object input) { + return ((Number) input).floatValue(); + } + }); + classMapping.put(long.class, new TypeConverter() { + public Long convert(Object input) { + return ((Number) input).longValue(); + } + }); + classMapping.put(Long.class, new TypeConverter() { + public Long convert(Object input) { + return ((Number) input).longValue(); + } + }); + classMapping.put(boolean.class, new TypeConverter() { + public Boolean convert(Object input) { + return (Boolean) input; + } + }); + classMapping.put(Boolean.class, new TypeConverter() { + public Boolean convert(Object input) { + return (Boolean) input; + } + }); + classMapping.put(String.class, new TypeConverter() { + public String convert(Object input) { + return (String) input; + } + }); + + collectionMapping.put(List.class, new InstanceCreator() { + public List create() { + return new ArrayList(); + } + }); + collectionMapping.put(Set.class, new InstanceCreator() { + public Set create() { + return new HashSet(); + } + }); + collectionMapping.put(Map.class, new InstanceCreator() { + 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, 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, InstanceCreator> getCollectionMapping() { + return collectionMapping; + } + + /** + * Adds or updates a type conversion function for a given class. + * + *

    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. + * + *

    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 JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration } } - /** - * 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 * @@ -3235,6 +3350,62 @@ private static JSONException recursivelyDefinedObjectException(String key) { ); } + /** + * Helper method to extract the raw Class from Type. + */ + 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 + } + return Object.class; // Fallback + } + + /** + * 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; + } + + /** + * 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; + } + } + return new Type[]{Object.class, Object.class}; // Default: String keys, Object values + } + + /** + * Deserializes a JSON string into an instance of the specified class. + * + *

    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 fromJson(String jsonString, Class clazz) { + JSONObject jsonObject = new JSONObject(jsonString); + return jsonObject.fromJson(clazz); + } + /** * Deserializes a JSON string into an instance of the specified class. * @@ -3245,122 +3416,160 @@ private static JSONException recursivelyDefinedObjectException(String key) { * in the JSON string. * * @param clazz the class of the object to be returned - * @param the type of the object * @return an instance of type T with fields populated from the JSON string */ + @SuppressWarnings("unchecked") public T fromJson(Class clazz) { - try { - T obj = clazz.getDeclaredConstructor().newInstance(); - if (this.builder == null) { - this.builder = new JSONBuilder(); - } - Map, 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) pojoClass, - field.getGenericType()); - - field.set(obj, nestedCollection); + 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(e); - } catch (InstantiationException e) { - throw new JSONException(e); - } catch (IllegalAccessException e) { - throw new JSONException(e); - } catch (InvocationTargetException e) { - throw new JSONException(e); - } + 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); + } } - private Collection fromJsonArray(JSONArray jsonArray, Class collectionType, Type elementType) throws JSONException { - try { - Map, TypeConverter> classMapping = this.builder.getClassMapping(); - Map, InstanceCreator> collectionMapping = this.builder.getCollectionMapping(); - Collection collection = (Collection) (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; + /** + * Handles non-primitive types (Enum, Map, JSONObject, JSONArray) during deserialization. + * Now dispatches to the recursive convertValue for consistency. + */ + private 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; } - 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)) { + Class rawType = getRawType(targetType); + + // Direct assignment + if (rawType.isAssignableFrom(value.getClass())) { + return value; + } - Collection nestedCollection = fromJsonArray((JSONArray) jsonElement, - innerElementClass, - innerElementType); + // Use registered type converter + if (classMapping.containsKey(rawType)) { + return classMapping.get(rawType).convert(value); + } - collection.add((T) nestedCollection); - } else { - throw new JSONException("Expected collection type for nested JSONArray, but got: " + innerElementClass); + // Enum conversion + if (rawType.isEnum() && value instanceof String) { + return stringToEnum(rawType, (String) value); + } + + // Collection handling (e.g., List>>) + if (Collection.class.isAssignableFrom(rawType)) { + if (value instanceof JSONArray) { + Type elementType = getElementType(targetType); + return fromJsonArray((JSONArray) value, rawType, elementType); } - } 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); - } + } + // Map handling (e.g., Map>) + 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) + 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(); } /** - * Deserializes a JSON string into an instance of the specified class. - * - *

    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 object JSONObject of internal class - * @param clazz the class of the object to be returned - * @param the type of the object - * @return an instance of type T with fields populated from the JSON string + * Converts a JSONObject to a Map with the specified generic key and value Types. + * Supports nested types via recursive convertValue. */ - private T fromJson(JSONObject object, Class clazz) { - return object.fromJson(clazz); + private Map convertToMap(JSONObject jsonMap, Type keyType, Type valueType, Class mapType) throws JSONException { + try { + InstanceCreator creator = collectionMapping.getOrDefault(mapType, () -> new HashMap<>()); + @SuppressWarnings("unchecked") + Map map = (Map) 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) + 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 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 Collection fromJsonArray(JSONArray jsonArray, Class collectionType, Type elementType) throws JSONException { + try { + InstanceCreator creator = collectionMapping.getOrDefault(collectionType, () -> new ArrayList<>()); + Collection collection = (Collection) 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); + } + } + } diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index 5a7aedb7c..f853d242a 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -33,7 +33,6 @@ 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.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 void jsonObjectParseFromJson_0() { 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() { + JSONObject object = new JSONObject(); + object.setClassMapping(java.time.LocalDateTime.class, new TypeConverter() { 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 java.time.LocalDateTime convert(Object input) { 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 void jsonObjectParseFromJson_2() { 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 void jsonObjectParseFromJson_3() { assertEquals(customClassD, compareClassD); } - public static class CustomClassD { - public List stringList; - - public CustomClassD() {} - public CustomClassD(List 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 void jsonObjectParseFromJson_4() { assertEquals(customClassE, compareClassE); } - public static class CustomClassE { - public List listClassC; - - public CustomClassE() {} - public CustomClassE(List 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 void jsonObjectParseFromJson_5() { assertEquals(customClassF, compareClassF); } - public static class CustomClassF { - public List> listOfString; + @Test + public void jsonObjectParseFromJson_6() { + JSONObject object = new JSONObject(); + Map dataList = new HashMap<>(); + dataList.put("A", "Aa"); + dataList.put("B", "Bb"); + dataList.put("C", "Cc"); + object.put("dataList", dataList); + + CustomClassG customClassG = object.fromJson(CustomClassG.class); + CustomClassG compareClassG = new CustomClassG(dataList); + assertEquals(customClassG, compareClassG); + } - public CustomClassF() {} - public CustomClassF(List> listOfString) { - this.listOfString = listOfString; - } + @Test + public void jsonObjectParseFromJson_7() { + JSONObject object = new JSONObject(); + Map> 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); - @Override - public boolean equals(Object o) { - CustomClassF classF = (CustomClassF) o; - return this.listOfString.equals(classF.listOfString); - } + 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> 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()); } } diff --git a/src/test/java/org/json/junit/data/CustomClass.java b/src/test/java/org/json/junit/data/CustomClass.java new file mode 100644 index 000000000..9ae405597 --- /dev/null +++ b/src/test/java/org/json/junit/data/CustomClass.java @@ -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)); + } +} + diff --git a/src/test/java/org/json/junit/data/CustomClassA.java b/src/test/java/org/json/junit/data/CustomClassA.java new file mode 100644 index 000000000..275e9a597 --- /dev/null +++ b/src/test/java/org/json/junit/data/CustomClassA.java @@ -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); + } +} + diff --git a/src/test/java/org/json/junit/data/CustomClassB.java b/src/test/java/org/json/junit/data/CustomClassB.java new file mode 100644 index 000000000..688997ec4 --- /dev/null +++ b/src/test/java/org/json/junit/data/CustomClassB.java @@ -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); + } +} + diff --git a/src/test/java/org/json/junit/data/CustomClassC.java b/src/test/java/org/json/junit/data/CustomClassC.java new file mode 100644 index 000000000..9d20aa392 --- /dev/null +++ b/src/test/java/org/json/junit/data/CustomClassC.java @@ -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); + } +} + diff --git a/src/test/java/org/json/junit/data/CustomClassD.java b/src/test/java/org/json/junit/data/CustomClassD.java new file mode 100644 index 000000000..4a858058c --- /dev/null +++ b/src/test/java/org/json/junit/data/CustomClassD.java @@ -0,0 +1,19 @@ +package org.json.junit.data; + +import java.util.List; + +public class CustomClassD { + public List stringList; + + public CustomClassD() {} + public CustomClassD(List stringList) { + this.stringList = stringList; + } + + @Override + public boolean equals(Object o) { + CustomClassD classD = (CustomClassD) o; + return this.stringList.equals(classD.stringList); + } +} + diff --git a/src/test/java/org/json/junit/data/CustomClassE.java b/src/test/java/org/json/junit/data/CustomClassE.java new file mode 100644 index 000000000..807dc5540 --- /dev/null +++ b/src/test/java/org/json/junit/data/CustomClassE.java @@ -0,0 +1,18 @@ +package org.json.junit.data; + +import java.util.List; + +public class CustomClassE { + public List listClassC; + + public CustomClassE() {} + public CustomClassE(List listClassC) { + this.listClassC = listClassC; + } + + @Override + public boolean equals(Object o) { + CustomClassE classE = (CustomClassE) o; + return this.listClassC.equals(classE.listClassC); + } +} diff --git a/src/test/java/org/json/junit/data/CustomClassF.java b/src/test/java/org/json/junit/data/CustomClassF.java new file mode 100644 index 000000000..d85861036 --- /dev/null +++ b/src/test/java/org/json/junit/data/CustomClassF.java @@ -0,0 +1,19 @@ +package org.json.junit.data; + +import java.util.List; + +public class CustomClassF { + public List> listOfString; + + public CustomClassF() {} + public CustomClassF(List> listOfString) { + this.listOfString = listOfString; + } + + @Override + public boolean equals(Object o) { + CustomClassF classF = (CustomClassF) o; + return this.listOfString.equals(classF.listOfString); + } +} + diff --git a/src/test/java/org/json/junit/data/CustomClassG.java b/src/test/java/org/json/junit/data/CustomClassG.java new file mode 100644 index 000000000..c8c9f5784 --- /dev/null +++ b/src/test/java/org/json/junit/data/CustomClassG.java @@ -0,0 +1,18 @@ +package org.json.junit.data; + +import java.util.Map; + +public class CustomClassG { + public Map dataList; + + public CustomClassG () {} + public CustomClassG (Map dataList) { + this.dataList = dataList; + } + + @Override + public boolean equals(Object object) { + CustomClassG classG = (CustomClassG) object; + return this.dataList.equals(classG.dataList); + } +} diff --git a/src/test/java/org/json/junit/data/CustomClassH.java b/src/test/java/org/json/junit/data/CustomClassH.java new file mode 100644 index 000000000..ce9b1af23 --- /dev/null +++ b/src/test/java/org/json/junit/data/CustomClassH.java @@ -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> integerMap; + + public CustomClassH() {} + public CustomClassH(Map> 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())); + } +} diff --git a/src/test/java/org/json/junit/data/CustomClassI.java b/src/test/java/org/json/junit/data/CustomClassI.java new file mode 100644 index 000000000..bd7c4ed89 --- /dev/null +++ b/src/test/java/org/json/junit/data/CustomClassI.java @@ -0,0 +1,12 @@ +package org.json.junit.data; + +import java.util.Map; + +public class CustomClassI { + public Map> integerMap; + + public CustomClassI() {} + public CustomClassI(Map> integerMap) { + this.integerMap = integerMap; + } +} From 7465da858c921a9e8e791bdaa54df35ea89697da Mon Sep 17 00:00:00 2001 From: sk02241994 Date: Sun, 28 Sep 2025 19:38:52 +1000 Subject: [PATCH 06/12] - Updating for java 1.6 - Resolving Sonar cube issues. --- src/main/java/org/json/JSONObject.java | 48 +++++++------------ .../java/org/json/junit/JSONObjectTest.java | 1 - 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 52bd2fedc..e1dfa4763 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -258,7 +258,7 @@ public Map create() { * @return a map of classes to functions that convert an {@code Object} to that class */ public Map, TypeConverter> getClassMapping() { - return this.classMapping; + return classMapping; } /** @@ -3445,20 +3445,6 @@ public T fromJson(Class clazz) { } } - /** - * Handles non-primitive types (Enum, Map, JSONObject, JSONArray) during deserialization. - * Now dispatches to the recursive convertValue for consistency. - */ - private 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. */ @@ -3492,20 +3478,16 @@ private Object convertValue(Object value, Type targetType) throws JSONException } } // Map handling (e.g., Map>) - 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); - } + else if (Map.class.isAssignableFrom(rawType) && 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) - else if (!rawType.isPrimitive() && !rawType.isEnum()) { - if (value instanceof JSONObject) { - // Recurse with the raw class for POJO deserialization - return ((JSONObject) value).fromJson(rawType); - } + else if (!rawType.isPrimitive() && !rawType.isEnum() && value instanceof JSONObject) { + // Recurse with the raw class for POJO deserialization + return ((JSONObject) value).fromJson(rawType); } // Fallback @@ -3520,7 +3502,7 @@ else if (!rawType.isPrimitive() && !rawType.isEnum()) { try { InstanceCreator creator = collectionMapping.getOrDefault(mapType, () -> new HashMap<>()); @SuppressWarnings("unchecked") - Map map = (Map) creator.create(); + Map createdMap = (Map) creator.create(); for (Object keyObj : jsonMap.keySet()) { String keyStr = (String) keyObj; @@ -3529,9 +3511,9 @@ else if (!rawType.isPrimitive() && !rawType.isEnum()) { Object convertedKey = convertValue(keyStr, keyType); // Convert value recursively (handles nesting) Object convertedValue = convertValue(mapValue, valueType); - map.put(convertedKey, convertedValue); + createdMap.put(convertedKey, convertedValue); } - return map; + return createdMap; } catch (Exception e) { throw new JSONException("Failed to convert JSONObject to Map: " + mapType.getName(), e); } @@ -3557,7 +3539,11 @@ private > E stringToEnum(Class enumClass, String value) thr @SuppressWarnings("unchecked") private Collection fromJsonArray(JSONArray jsonArray, Class collectionType, Type elementType) throws JSONException { try { - InstanceCreator creator = collectionMapping.getOrDefault(collectionType, () -> new ArrayList<>()); + InstanceCreator creator = collectionMapping.getOrDefault(collectionType, new InstanceCreator() { + public List create() { + return new ArrayList(); + } + }); Collection collection = (Collection) creator.create(); for (int i = 0; i < jsonArray.length(); i++) { diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index f853d242a..7b8154198 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -4114,7 +4114,6 @@ public void jsonObjectParseFromJson_0() { object.put("number", 12); object.put("name", "Alex"); object.put("longNumber", 1500000000L); - String jsonObject = object.toString(); CustomClass customClass = object.fromJson(CustomClass.class); CustomClass compareClass = new CustomClass(12, "Alex", 1500000000L); assertEquals(customClass, compareClass); From 9adea9e12de03ec5d548967e7d3bee3ca02f76d7 Mon Sep 17 00:00:00 2001 From: sk02241994 Date: Sun, 28 Sep 2025 20:15:14 +1000 Subject: [PATCH 07/12] Updating to work with java 1.6 --- src/main/java/org/json/JSONObject.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index e1dfa4763..fa16c3aff 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -3440,7 +3440,7 @@ public T fromJson(Class clazz) { return obj; } catch (NoSuchMethodException e) { throw new JSONException("No no-arg constructor for class: " + clazz.getName(), e); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + } catch (Exception e) { throw new JSONException("Failed to instantiate or set field for class: " + clazz.getName(), e); } } @@ -3500,7 +3500,11 @@ else if (!rawType.isPrimitive() && !rawType.isEnum() && value instanceof JSONObj */ private Map convertToMap(JSONObject jsonMap, Type keyType, Type valueType, Class mapType) throws JSONException { try { - InstanceCreator creator = collectionMapping.getOrDefault(mapType, () -> new HashMap<>()); + InstanceCreator creator = collectionMapping.get(mapType) != null ? collectionMapping.get(mapType) : new InstanceCreator() { + public Map create() { + return new HashMap(); + } + }; @SuppressWarnings("unchecked") Map createdMap = (Map) creator.create(); @@ -3522,12 +3526,13 @@ else if (!rawType.isPrimitive() && !rawType.isEnum() && value instanceof JSONObj /** * Converts a String to an Enum value. */ - private > E stringToEnum(Class enumClass, String value) throws JSONException { + private E stringToEnum(Class enumClass, String value) throws JSONException { try { @SuppressWarnings("unchecked") - Method valueOfMethod = enumClass.getMethod("valueOf", String.class); + Class enumType = (Class) enumClass; + Method valueOfMethod = enumType.getMethod("valueOf", String.class); return (E) valueOfMethod.invoke(null, value); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + } catch (Exception e) { throw new JSONException("Failed to convert string to enum: " + value + " for " + enumClass.getName(), e); } } @@ -3539,11 +3544,11 @@ private > E stringToEnum(Class enumClass, String value) thr @SuppressWarnings("unchecked") private Collection fromJsonArray(JSONArray jsonArray, Class collectionType, Type elementType) throws JSONException { try { - InstanceCreator creator = collectionMapping.getOrDefault(collectionType, new InstanceCreator() { + InstanceCreator creator = collectionMapping.get(collectionType) != null ? collectionMapping.get(collectionType) : new InstanceCreator() { public List create() { return new ArrayList(); } - }); + }; Collection collection = (Collection) creator.create(); for (int i = 0; i < jsonArray.length(); i++) { From c4c2beb87450bf382b25b928f1610b0ac22b5412 Mon Sep 17 00:00:00 2001 From: sk02241994 Date: Thu, 16 Oct 2025 14:19:19 +1100 Subject: [PATCH 08/12] Limiting implemetation by removing the new classes. --- src/main/java/org/json/InstanceCreator.java | 2 +- src/main/java/org/json/JSONObject.java | 54 ++++--------------- src/main/java/org/json/TypeConverter.java | 2 +- .../java/org/json/junit/JSONObjectTest.java | 19 +++---- .../org/json/junit/data/CustomClassA.java | 10 ++-- 5 files changed, 25 insertions(+), 62 deletions(-) diff --git a/src/main/java/org/json/InstanceCreator.java b/src/main/java/org/json/InstanceCreator.java index 4836e23da..c8ae05c15 100644 --- a/src/main/java/org/json/InstanceCreator.java +++ b/src/main/java/org/json/InstanceCreator.java @@ -5,7 +5,7 @@ * * @param the type of instances created */ -public interface InstanceCreator { +interface InstanceCreator { /** * Creates a new instance of type {@code T}. diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index fa16c3aff..e0c033718 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -234,6 +234,16 @@ public String convert(Object input) { return (String) input; } }); + classMapping.put(BigDecimal.class, new TypeConverter() { + public BigDecimal convert(Object input) { + return new BigDecimal((String) input); + } + }); + classMapping.put(BigInteger.class, new TypeConverter() { + public BigInteger convert(Object input) { + return new BigInteger((String) input); + } + }); collectionMapping.put(List.class, new InstanceCreator() { public List create() { @@ -252,50 +262,6 @@ public Map create() { }); } - /** - * 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, TypeConverter> getClassMapping() { - return 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, InstanceCreator> getCollectionMapping() { - return collectionMapping; - } - - /** - * Adds or updates a type conversion function for a given class. - * - *

    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. - * - *

    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 diff --git a/src/main/java/org/json/TypeConverter.java b/src/main/java/org/json/TypeConverter.java index dc07325e3..d5b4eafe1 100644 --- a/src/main/java/org/json/TypeConverter.java +++ b/src/main/java/org/json/TypeConverter.java @@ -6,7 +6,7 @@ * * @param the target type to convert to */ -public interface TypeConverter { +interface TypeConverter { /** * Converts the given input object to an instance of type {@code T}. diff --git a/src/test/java/org/json/junit/JSONObjectTest.java b/src/test/java/org/json/junit/JSONObjectTest.java index 7b8154198..7ca6093b7 100644 --- a/src/test/java/org/json/junit/JSONObjectTest.java +++ b/src/test/java/org/json/junit/JSONObjectTest.java @@ -36,7 +36,6 @@ import org.json.JSONTokener; import org.json.ParserConfiguration; import org.json.XML; -import org.json.TypeConverter; import org.json.junit.data.BrokenToString; import org.json.junit.data.ExceptionalBean; import org.json.junit.data.Fraction; @@ -4121,17 +4120,13 @@ public void jsonObjectParseFromJson_0() { @Test public void jsonObjectParseFromJson_1() { - JSONObject object = new JSONObject(); - object.setClassMapping(java.time.LocalDateTime.class, new TypeConverter() { - public java.time.LocalDateTime convert(Object input) { - return java.time.LocalDateTime.parse((String) input); - } - }); - java.time.LocalDateTime localDateTime = java.time.LocalDateTime.now(); - object.put("localDate", localDateTime.toString()); - CustomClassA customClassA = object.fromJson(CustomClassA.class); - CustomClassA compareClassClassA = new CustomClassA(localDateTime); - assertEquals(customClassA, compareClassClassA); + JSONObject object = new JSONObject(); + + BigInteger largeInt = new BigInteger("123"); + object.put("largeInt", largeInt.toString()); + CustomClassA customClassA = object.fromJson(CustomClassA.class); + CustomClassA compareClassClassA = new CustomClassA(largeInt); + assertEquals(customClassA, compareClassClassA); } @Test diff --git a/src/test/java/org/json/junit/data/CustomClassA.java b/src/test/java/org/json/junit/data/CustomClassA.java index 275e9a597..08a99d333 100644 --- a/src/test/java/org/json/junit/data/CustomClassA.java +++ b/src/test/java/org/json/junit/data/CustomClassA.java @@ -1,17 +1,19 @@ package org.json.junit.data; +import java.math.BigInteger; + public class CustomClassA { - public java.time.LocalDateTime localDate; + public BigInteger largeInt; public CustomClassA() {} - public CustomClassA(java.time.LocalDateTime localDate) { - this.localDate = localDate; + public CustomClassA(BigInteger largeInt) { + this.largeInt = largeInt; } @Override public boolean equals(Object o) { CustomClassA classA = (CustomClassA) o; - return this.localDate.equals(classA.localDate); + return this.largeInt.equals(classA.largeInt); } } From a7c193090a36aecb78a80bfc150260a09f6ec338 Mon Sep 17 00:00:00 2001 From: sk02241994 Date: Thu, 16 Oct 2025 14:23:30 +1100 Subject: [PATCH 09/12] Updating docs --- src/main/java/org/json/JSONObject.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index e0c033718..e9e6ff5c0 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -3376,13 +3376,20 @@ public static T fromJson(String jsonString, Class clazz) { * Deserializes a JSON string into an instance of the specified class. * *

    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. + * of the given class. It supports basic data types including {@code int}, {@code double}, + * {@code float}, {@code long}, and {@code boolean}, as well as their boxed counterparts. + * The target class must have a no-argument constructor, and its field names must match + * the keys in the JSON string. + * + *

    Note: Only classes that are explicitly supported and registered within + * the {@code JSONObject} context can be deserialized. If the provided class is not among those, + * this method will not be able to deserialize it. This ensures that only a limited and + * controlled set of types can be instantiated from JSON for safety and predictability. * * @param clazz the class of the object to be returned - * @return an instance of type T with fields populated from the JSON string + * @param the type of the object + * @return an instance of type {@code T} with fields populated from the JSON string + * @throws IllegalArgumentException if the class is not supported for deserialization */ @SuppressWarnings("unchecked") public T fromJson(Class clazz) { From 8ccf5d7525487226d7a2362f67a36ca606aa6614 Mon Sep 17 00:00:00 2001 From: sk02241994 Date: Thu, 23 Oct 2025 17:32:07 +1100 Subject: [PATCH 10/12] Removing the interface classes and simplifying the implementation to use if else instead --- src/main/java/org/json/InstanceCreator.java | 16 -- src/main/java/org/json/JSONObject.java | 177 ++++++-------------- src/main/java/org/json/TypeConverter.java | 18 -- 3 files changed, 49 insertions(+), 162 deletions(-) delete mode 100644 src/main/java/org/json/InstanceCreator.java delete mode 100644 src/main/java/org/json/TypeConverter.java diff --git a/src/main/java/org/json/InstanceCreator.java b/src/main/java/org/json/InstanceCreator.java deleted file mode 100644 index c8ae05c15..000000000 --- a/src/main/java/org/json/InstanceCreator.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.json; - -/** - * Interface defining a creator that produces new instances of type {@code T}. - * - * @param the type of instances created - */ -interface InstanceCreator { - - /** - * Creates a new instance of type {@code T}. - * - * @return a new instance of {@code T} - */ - T create(); -} diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index e9e6ff5c0..934a45454 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -157,111 +157,6 @@ public JSONObject() { this.map = new HashMap(); } - /** - * A mapping from Java classes to functions that convert a generic {@code Object} - * into an instance of the target class. - * - *

    Examples of default mappings: - *

      - *
    • {@code int.class} or {@code Integer.class} -> Converts a {@code Number} to {@code int}
    • - *
    • {@code boolean.class} or {@code Boolean.class} -> Identity function
    • - *
    • {@code String.class} -> Identity function
    • - *
    - */ - private static final Map, TypeConverter> classMapping = new HashMap, TypeConverter>(); - - /** - * A mapping from collection interface types to suppliers that produce - * instances of concrete collection implementations. - * - */ - private static final Map, InstanceCreator> collectionMapping = new HashMap, InstanceCreator>(); - - // Static initializer block to populate default mappings - static { - classMapping.put(int.class, new TypeConverter() { - public Integer convert(Object input) { - return ((Number) input).intValue(); - } - }); - classMapping.put(Integer.class, new TypeConverter() { - public Integer convert(Object input) { - return ((Number) input).intValue(); - } - }); - classMapping.put(double.class, new TypeConverter() { - public Double convert(Object input) { - return ((Number) input).doubleValue(); - } - }); - classMapping.put(Double.class, new TypeConverter() { - public Double convert(Object input) { - return ((Number) input).doubleValue(); - } - }); - classMapping.put(float.class, new TypeConverter() { - public Float convert(Object input) { - return ((Number) input).floatValue(); - } - }); - classMapping.put(Float.class, new TypeConverter() { - public Float convert(Object input) { - return ((Number) input).floatValue(); - } - }); - classMapping.put(long.class, new TypeConverter() { - public Long convert(Object input) { - return ((Number) input).longValue(); - } - }); - classMapping.put(Long.class, new TypeConverter() { - public Long convert(Object input) { - return ((Number) input).longValue(); - } - }); - classMapping.put(boolean.class, new TypeConverter() { - public Boolean convert(Object input) { - return (Boolean) input; - } - }); - classMapping.put(Boolean.class, new TypeConverter() { - public Boolean convert(Object input) { - return (Boolean) input; - } - }); - classMapping.put(String.class, new TypeConverter() { - public String convert(Object input) { - return (String) input; - } - }); - classMapping.put(BigDecimal.class, new TypeConverter() { - public BigDecimal convert(Object input) { - return new BigDecimal((String) input); - } - }); - classMapping.put(BigInteger.class, new TypeConverter() { - public BigInteger convert(Object input) { - return new BigInteger((String) input); - } - }); - - collectionMapping.put(List.class, new InstanceCreator() { - public List create() { - return new ArrayList(); - } - }); - collectionMapping.put(Set.class, new InstanceCreator() { - public Set create() { - return new HashSet(); - } - }); - collectionMapping.put(Map.class, new InstanceCreator() { - public Map create() { - return new HashMap(); - } - }); - } - /** * 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 @@ -3359,8 +3254,8 @@ private Type[] getMapTypes(Type type) { * *

    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 + * 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 @@ -3402,12 +3297,8 @@ public T fromJson(Class clazz) { 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); - } + Object convertedValue = convertValue(value, fieldType); + field.set(obj, convertedValue); } } return obj; @@ -3433,9 +3324,22 @@ private Object convertValue(Object value, Type targetType) throws JSONException return value; } - // Use registered type converter - if (classMapping.containsKey(rawType)) { - return classMapping.get(rawType).convert(value); + if (rawType == int.class || rawType == Integer.class) { + return ((Number) value).intValue(); + } else if (rawType == double.class || rawType == Double.class) { + return ((Number) value).doubleValue(); + } else if (rawType == float.class || rawType == Float.class) { + return ((Number) value).floatValue(); + } else if (rawType == long.class || rawType == Long.class) { + return ((Number) value).longValue(); + } else if (rawType == boolean.class || rawType == Boolean.class) { + return (Boolean) value; + } else if (rawType == String.class) { + return (String) value; + } else if (rawType == BigDecimal.class) { + return new BigDecimal((String) value); + } else if (rawType == BigInteger.class) { + return new BigInteger((String) value); } // Enum conversion @@ -3473,13 +3377,8 @@ else if (!rawType.isPrimitive() && !rawType.isEnum() && value instanceof JSONObj */ private Map convertToMap(JSONObject jsonMap, Type keyType, Type valueType, Class mapType) throws JSONException { try { - InstanceCreator creator = collectionMapping.get(mapType) != null ? collectionMapping.get(mapType) : new InstanceCreator() { - public Map create() { - return new HashMap(); - } - }; @SuppressWarnings("unchecked") - Map createdMap = (Map) creator.create(); + Map createdMap = new HashMap(); for (Object keyObj : jsonMap.keySet()) { String keyStr = (String) keyObj; @@ -3517,12 +3416,7 @@ private E stringToEnum(Class enumClass, String value) throws JSONExceptio @SuppressWarnings("unchecked") private Collection fromJsonArray(JSONArray jsonArray, Class collectionType, Type elementType) throws JSONException { try { - InstanceCreator creator = collectionMapping.get(collectionType) != null ? collectionMapping.get(collectionType) : new InstanceCreator() { - public List create() { - return new ArrayList(); - } - }; - Collection collection = (Collection) creator.create(); + Collection collection = getCollection(collectionType); for (int i = 0; i < jsonArray.length(); i++) { Object jsonElement = jsonArray.get(i); @@ -3536,4 +3430,31 @@ public List create() { } } + /** + * Creates and returns a new instance of a supported {@link Collection} implementation + * based on the specified collection type. + *

    + * This method currently supports the following collection types: + *

      + *
    • {@code List.class}
    • + *
    • {@code ArrayList.class}
    • + *
    • {@code Set.class}
    • + *
    • {@code HashSet.class}
    • + *
    + * If the provided type does not match any of the supported types, a {@link JSONException} + * is thrown. + * + * @param collectionType the {@link Class} object representing the desired collection type + * @return a new empty instance of the specified collection type + * @throws JSONException if the specified type is not a supported collection type + */ + private Collection getCollection(Class collectionType) throws JSONException { + if (collectionType == List.class || collectionType == ArrayList.class) { + return new ArrayList<>(); + } else if (collectionType == Set.class || collectionType == HashSet.class) { + return new HashSet<>(); + } else { + throw new JSONException("Unsupported Collection type: " + collectionType.getName()); + } + } } diff --git a/src/main/java/org/json/TypeConverter.java b/src/main/java/org/json/TypeConverter.java deleted file mode 100644 index d5b4eafe1..000000000 --- a/src/main/java/org/json/TypeConverter.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.json; - -/** - * Interface defining a converter that converts an input {@code Object} - * into an instance of a specific type {@code T}. - * - * @param the target type to convert to - */ -interface TypeConverter { - - /** - * Converts the given input object to an instance of type {@code T}. - * - * @param input the object to convert - * @return the converted instance of type {@code T} - */ - T convert(Object input); -} From f92f28162033ce16b42c207ad393a9898ddca23b Mon Sep 17 00:00:00 2001 From: sk02241994 Date: Thu, 23 Oct 2025 17:33:37 +1100 Subject: [PATCH 11/12] Updating to work with java 1.6 --- src/main/java/org/json/JSONObject.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 934a45454..1e90e69d7 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -3450,9 +3450,9 @@ private Collection fromJsonArray(JSONArray jsonArray, Class collection */ private Collection getCollection(Class collectionType) throws JSONException { if (collectionType == List.class || collectionType == ArrayList.class) { - return new ArrayList<>(); + return new ArrayList(); } else if (collectionType == Set.class || collectionType == HashSet.class) { - return new HashSet<>(); + return new HashSet(); } else { throw new JSONException("Unsupported Collection type: " + collectionType.getName()); } From 42800c208a969d9151af50b64dcdfb7a6cacd9df Mon Sep 17 00:00:00 2001 From: sk02241994 Date: Tue, 28 Oct 2025 13:06:11 +1100 Subject: [PATCH 12/12] Updating to work with java 1.6 --- src/main/java/org/json/JSONObject.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/json/JSONObject.java b/src/main/java/org/json/JSONObject.java index 1e90e69d7..4e8b42c97 100644 --- a/src/main/java/org/json/JSONObject.java +++ b/src/main/java/org/json/JSONObject.java @@ -3296,7 +3296,6 @@ public T fromJson(Class clazz) { if (has(fieldName)) { Object value = get(fieldName); Type fieldType = field.getGenericType(); - Class rawType = getRawType(fieldType); Object convertedValue = convertValue(value, fieldType); field.set(obj, convertedValue); } @@ -3333,9 +3332,9 @@ private Object convertValue(Object value, Type targetType) throws JSONException } else if (rawType == long.class || rawType == Long.class) { return ((Number) value).longValue(); } else if (rawType == boolean.class || rawType == Boolean.class) { - return (Boolean) value; + return value; } else if (rawType == String.class) { - return (String) value; + return value; } else if (rawType == BigDecimal.class) { return new BigDecimal((String) value); } else if (rawType == BigInteger.class) { @@ -3353,14 +3352,14 @@ private Object convertValue(Object value, Type targetType) throws JSONException Type elementType = getElementType(targetType); return fromJsonArray((JSONArray) value, rawType, elementType); } - } + } // Map handling (e.g., Map>) else if (Map.class.isAssignableFrom(rawType) && 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) else if (!rawType.isPrimitive() && !rawType.isEnum() && value instanceof JSONObject) { // Recurse with the raw class for POJO deserialization