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 257eb10..496a15a 100644
--- a/src/main/java/org/json/JSONObject.java
+++ b/src/main/java/org/json/JSONObject.java
@@ -17,6 +17,10 @@ import java.math.BigInteger;
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 class JSONObject {
*/
static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?");
+
+ /**
+ * A Builder class for handling the conversion of JSON to Object.
+ */
+ private JSONBuilder builder;
+
/**
* The map where the JSONObject's properties are kept.
*/
@@ -212,6 +222,25 @@ public class JSONObject {
}
}
+ /**
+ * Construct a JSONObject with JSONBuilder for conversion from JSON to POJO
+ *
+ * @param builder builder option for json to POJO
+ */
+ public JSONObject(JSONBuilder builder) {
+ this();
+ this.builder = builder;
+ }
+
+ /**
+ * Method to set JSONBuilder.
+ *
+ * @param builder
+ */
+ public void setJSONBuilder(JSONBuilder builder) {
+ this.builder = builder;
+ }
+
/**
* Parses entirety of JSON object
*
@@ -3207,4 +3236,121 @@ public class JSONObject {
"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 extends Collection>) 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 88c19c7..e3fb1d8 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.JSONObject;
import org.json.JSONPointerException;
import org.json.JSONParserConfiguration;
import org.json.JSONString;
+import org.json.JSONBuilder;
import org.json.JSONTokener;
import org.json.ParserConfiguration;
import org.json.XML;
@@ -4095,4 +4096,219 @@ public class JSONObjectTest {
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);
+ }
+ }
}