|
17 | 17 | import java.util.*; |
18 | 18 | import java.util.Map.Entry; |
19 | 19 | import java.util.regex.Pattern; |
| 20 | +import java.util.function.Function; |
| 21 | +import java.util.function.Supplier; |
| 22 | +import java.lang.reflect.ParameterizedType; |
| 23 | +import java.lang.reflect.Type; |
20 | 24 |
|
21 | 25 | /** |
22 | 26 | * A JSONObject is an unordered collection of name/value pairs. Its external |
@@ -119,6 +123,12 @@ public String toString() { |
119 | 123 | */ |
120 | 124 | static final Pattern NUMBER_PATTERN = Pattern.compile("-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?"); |
121 | 125 |
|
| 126 | + |
| 127 | + /** |
| 128 | + * A Builder class for handling the conversion of JSON to Object. |
| 129 | + */ |
| 130 | + private JSONBuilder builder; |
| 131 | + |
122 | 132 | /** |
123 | 133 | * The map where the JSONObject's properties are kept. |
124 | 134 | */ |
@@ -212,6 +222,25 @@ public JSONObject(JSONTokener x, JSONParserConfiguration jsonParserConfiguration |
212 | 222 | } |
213 | 223 | } |
214 | 224 |
|
| 225 | + /** |
| 226 | + * Construct a JSONObject with JSONBuilder for conversion from JSON to POJO |
| 227 | + * |
| 228 | + * @param builder builder option for json to POJO |
| 229 | + */ |
| 230 | + public JSONObject(JSONBuilder builder) { |
| 231 | + this(); |
| 232 | + this.builder = builder; |
| 233 | + } |
| 234 | + |
| 235 | + /** |
| 236 | + * Method to set JSONBuilder. |
| 237 | + * |
| 238 | + * @param builder |
| 239 | + */ |
| 240 | + public void setJSONBuilder(JSONBuilder builder) { |
| 241 | + this.builder = builder; |
| 242 | + } |
| 243 | + |
215 | 244 | /** |
216 | 245 | * Parses entirety of JSON object |
217 | 246 | * |
@@ -3110,4 +3139,121 @@ private static JSONException recursivelyDefinedObjectException(String key) { |
3110 | 3139 | "JavaBean object contains recursively defined member variable of key " + quote(key) |
3111 | 3140 | ); |
3112 | 3141 | } |
| 3142 | + |
| 3143 | + /** |
| 3144 | + * Deserializes a JSON string into an instance of the specified class. |
| 3145 | + * |
| 3146 | + * <p>This method attempts to map JSON key-value pairs to the corresponding fields |
| 3147 | + * of the given class. It supports basic data types including int, double, float, |
| 3148 | + * long, and boolean (as well as their boxed counterparts). The class must have a |
| 3149 | + * no-argument constructor, and the field names in the class must match the keys |
| 3150 | + * in the JSON string. |
| 3151 | + * |
| 3152 | + * @param clazz the class of the object to be returned |
| 3153 | + * @param <T> the type of the object |
| 3154 | + * @return an instance of type T with fields populated from the JSON string |
| 3155 | + */ |
| 3156 | + public <T> T fromJson(Class<T> clazz) { |
| 3157 | + try { |
| 3158 | + T obj = clazz.getDeclaredConstructor().newInstance(); |
| 3159 | + if (this.builder == null) { |
| 3160 | + this.builder = new JSONBuilder(); |
| 3161 | + } |
| 3162 | + Map<Class<?>, Function<Object, ?>> classMapping = this.builder.getClassMapping(); |
| 3163 | + |
| 3164 | + for (Field field: clazz.getDeclaredFields()) { |
| 3165 | + field.setAccessible(true); |
| 3166 | + String fieldName = field.getName(); |
| 3167 | + if (this.has(fieldName)) { |
| 3168 | + Object value = this.get(fieldName); |
| 3169 | + Class<?> pojoClass = field.getType(); |
| 3170 | + if (classMapping.containsKey(pojoClass)) { |
| 3171 | + field.set(obj, classMapping.get(pojoClass).apply(value)); |
| 3172 | + } else { |
| 3173 | + if (value.getClass() == JSONObject.class) { |
| 3174 | + field.set(obj, fromJson((JSONObject) value, pojoClass)); |
| 3175 | + } else if (value.getClass() == JSONArray.class) { |
| 3176 | + if (Collection.class.isAssignableFrom(pojoClass)) { |
| 3177 | + |
| 3178 | + Collection<?> nestedCollection = fromJsonArray((JSONArray) value, |
| 3179 | + (Class<? extends Collection>) pojoClass, |
| 3180 | + field.getGenericType()); |
| 3181 | + |
| 3182 | + field.set(obj, nestedCollection); |
| 3183 | + } |
| 3184 | + } |
| 3185 | + } |
| 3186 | + } |
| 3187 | + } |
| 3188 | + return obj; |
| 3189 | + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { |
| 3190 | + throw new JSONException(e); |
| 3191 | + } |
| 3192 | + } |
| 3193 | + |
| 3194 | + private <T> Collection<T> fromJsonArray(JSONArray jsonArray, Class<?> collectionType, Type elementType) throws JSONException { |
| 3195 | + try { |
| 3196 | + Map<Class<?>, Function<Object, ?>> classMapping = this.builder.getClassMapping(); |
| 3197 | + Map<Class<?>, Supplier<?>> collectionMapping = this.builder.getCollectionMapping(); |
| 3198 | + Collection<T> collection = (Collection<T>) (collectionMapping.containsKey(collectionType) ? |
| 3199 | + collectionMapping.get(collectionType).get() |
| 3200 | + : collectionType.getDeclaredConstructor().newInstance()); |
| 3201 | + |
| 3202 | + |
| 3203 | + Class<?> innerElementClass = null; |
| 3204 | + Type innerElementType = null; |
| 3205 | + if (elementType instanceof ParameterizedType) { |
| 3206 | + ParameterizedType pType = (ParameterizedType) elementType; |
| 3207 | + innerElementType = pType.getActualTypeArguments()[0]; |
| 3208 | + innerElementClass = (innerElementType instanceof Class) ? |
| 3209 | + (Class<?>) innerElementType |
| 3210 | + : (Class<?>) ((ParameterizedType) innerElementType).getRawType(); |
| 3211 | + } else { |
| 3212 | + innerElementClass = (Class<?>) elementType; |
| 3213 | + } |
| 3214 | + |
| 3215 | + for (int i = 0; i < jsonArray.length(); i++) { |
| 3216 | + Object jsonElement = jsonArray.get(i); |
| 3217 | + if (classMapping.containsKey(innerElementClass)) { |
| 3218 | + collection.add((T) classMapping.get(innerElementClass).apply(jsonElement)); |
| 3219 | + } else if (jsonElement.getClass() == JSONObject.class) { |
| 3220 | + collection.add((T) ((JSONObject) jsonElement).fromJson(innerElementClass)); |
| 3221 | + } else if (jsonElement.getClass() == JSONArray.class) { |
| 3222 | + if (Collection.class.isAssignableFrom(innerElementClass)) { |
| 3223 | + |
| 3224 | + Collection<?> nestedCollection = fromJsonArray((JSONArray) jsonElement, |
| 3225 | + innerElementClass, |
| 3226 | + innerElementType); |
| 3227 | + |
| 3228 | + collection.add((T) nestedCollection); |
| 3229 | + } else { |
| 3230 | + throw new JSONException("Expected collection type for nested JSONArray, but got: " + innerElementClass); |
| 3231 | + } |
| 3232 | + } else { |
| 3233 | + collection.add((T) jsonElement.toString()); |
| 3234 | + } |
| 3235 | + } |
| 3236 | + return collection; |
| 3237 | + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { |
| 3238 | + throw new JSONException(e); |
| 3239 | + } |
| 3240 | + } |
| 3241 | + |
| 3242 | + /** |
| 3243 | + * Deserializes a JSON string into an instance of the specified class. |
| 3244 | + * |
| 3245 | + * <p>This method attempts to map JSON key-value pairs to the corresponding fields |
| 3246 | + * of the given class. It supports basic data types including int, double, float, |
| 3247 | + * long, and boolean (as well as their boxed counterparts). The class must have a |
| 3248 | + * no-argument constructor, and the field names in the class must match the keys |
| 3249 | + * in the JSON string. |
| 3250 | + * |
| 3251 | + * @param object JSONObject of internal class |
| 3252 | + * @param clazz the class of the object to be returned |
| 3253 | + * @param <T> the type of the object |
| 3254 | + * @return an instance of type T with fields populated from the JSON string |
| 3255 | + */ |
| 3256 | + private <T> T fromJson(JSONObject object, Class<T> clazz) { |
| 3257 | + return object.fromJson(clazz); |
| 3258 | + } |
3113 | 3259 | } |
0 commit comments