|
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 | * |
@@ -3207,4 +3236,121 @@ private static JSONException recursivelyDefinedObjectException(String key) { |
3207 | 3236 | "JavaBean object contains recursively defined member variable of key " + quote(key) |
3208 | 3237 | ); |
3209 | 3238 | } |
| 3239 | + |
| 3240 | + /** |
| 3241 | + * Deserializes a JSON string into an instance of the specified class. |
| 3242 | + * |
| 3243 | + * <p>This method attempts to map JSON key-value pairs to the corresponding fields |
| 3244 | + * of the given class. It supports basic data types including int, double, float, |
| 3245 | + * long, and boolean (as well as their boxed counterparts). The class must have a |
| 3246 | + * no-argument constructor, and the field names in the class must match the keys |
| 3247 | + * in the JSON string. |
| 3248 | + * |
| 3249 | + * @param clazz the class of the object to be returned |
| 3250 | + * @param <T> the type of the object |
| 3251 | + * @return an instance of type T with fields populated from the JSON string |
| 3252 | + */ |
| 3253 | + public <T> T fromJson(Class<T> clazz) { |
| 3254 | + try { |
| 3255 | + T obj = clazz.getDeclaredConstructor().newInstance(); |
| 3256 | + if (this.builder == null) { |
| 3257 | + this.builder = new JSONBuilder(); |
| 3258 | + } |
| 3259 | + Map<Class<?>, Function<Object, ?>> classMapping = this.builder.getClassMapping(); |
| 3260 | + |
| 3261 | + for (Field field: clazz.getDeclaredFields()) { |
| 3262 | + field.setAccessible(true); |
| 3263 | + String fieldName = field.getName(); |
| 3264 | + if (this.has(fieldName)) { |
| 3265 | + Object value = this.get(fieldName); |
| 3266 | + Class<?> pojoClass = field.getType(); |
| 3267 | + if (classMapping.containsKey(pojoClass)) { |
| 3268 | + field.set(obj, classMapping.get(pojoClass).apply(value)); |
| 3269 | + } else { |
| 3270 | + if (value.getClass() == JSONObject.class) { |
| 3271 | + field.set(obj, fromJson((JSONObject) value, pojoClass)); |
| 3272 | + } else if (value.getClass() == JSONArray.class) { |
| 3273 | + if (Collection.class.isAssignableFrom(pojoClass)) { |
| 3274 | + |
| 3275 | + Collection<?> nestedCollection = fromJsonArray((JSONArray) value, |
| 3276 | + (Class<? extends Collection>) pojoClass, |
| 3277 | + field.getGenericType()); |
| 3278 | + |
| 3279 | + field.set(obj, nestedCollection); |
| 3280 | + } |
| 3281 | + } |
| 3282 | + } |
| 3283 | + } |
| 3284 | + } |
| 3285 | + return obj; |
| 3286 | + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { |
| 3287 | + throw new JSONException(e); |
| 3288 | + } |
| 3289 | + } |
| 3290 | + |
| 3291 | + private <T> Collection<T> fromJsonArray(JSONArray jsonArray, Class<?> collectionType, Type elementType) throws JSONException { |
| 3292 | + try { |
| 3293 | + Map<Class<?>, Function<Object, ?>> classMapping = this.builder.getClassMapping(); |
| 3294 | + Map<Class<?>, Supplier<?>> collectionMapping = this.builder.getCollectionMapping(); |
| 3295 | + Collection<T> collection = (Collection<T>) (collectionMapping.containsKey(collectionType) ? |
| 3296 | + collectionMapping.get(collectionType).get() |
| 3297 | + : collectionType.getDeclaredConstructor().newInstance()); |
| 3298 | + |
| 3299 | + |
| 3300 | + Class<?> innerElementClass = null; |
| 3301 | + Type innerElementType = null; |
| 3302 | + if (elementType instanceof ParameterizedType) { |
| 3303 | + ParameterizedType pType = (ParameterizedType) elementType; |
| 3304 | + innerElementType = pType.getActualTypeArguments()[0]; |
| 3305 | + innerElementClass = (innerElementType instanceof Class) ? |
| 3306 | + (Class<?>) innerElementType |
| 3307 | + : (Class<?>) ((ParameterizedType) innerElementType).getRawType(); |
| 3308 | + } else { |
| 3309 | + innerElementClass = (Class<?>) elementType; |
| 3310 | + } |
| 3311 | + |
| 3312 | + for (int i = 0; i < jsonArray.length(); i++) { |
| 3313 | + Object jsonElement = jsonArray.get(i); |
| 3314 | + if (classMapping.containsKey(innerElementClass)) { |
| 3315 | + collection.add((T) classMapping.get(innerElementClass).apply(jsonElement)); |
| 3316 | + } else if (jsonElement.getClass() == JSONObject.class) { |
| 3317 | + collection.add((T) ((JSONObject) jsonElement).fromJson(innerElementClass)); |
| 3318 | + } else if (jsonElement.getClass() == JSONArray.class) { |
| 3319 | + if (Collection.class.isAssignableFrom(innerElementClass)) { |
| 3320 | + |
| 3321 | + Collection<?> nestedCollection = fromJsonArray((JSONArray) jsonElement, |
| 3322 | + innerElementClass, |
| 3323 | + innerElementType); |
| 3324 | + |
| 3325 | + collection.add((T) nestedCollection); |
| 3326 | + } else { |
| 3327 | + throw new JSONException("Expected collection type for nested JSONArray, but got: " + innerElementClass); |
| 3328 | + } |
| 3329 | + } else { |
| 3330 | + collection.add((T) jsonElement.toString()); |
| 3331 | + } |
| 3332 | + } |
| 3333 | + return collection; |
| 3334 | + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { |
| 3335 | + throw new JSONException(e); |
| 3336 | + } |
| 3337 | + } |
| 3338 | + |
| 3339 | + /** |
| 3340 | + * Deserializes a JSON string into an instance of the specified class. |
| 3341 | + * |
| 3342 | + * <p>This method attempts to map JSON key-value pairs to the corresponding fields |
| 3343 | + * of the given class. It supports basic data types including int, double, float, |
| 3344 | + * long, and boolean (as well as their boxed counterparts). The class must have a |
| 3345 | + * no-argument constructor, and the field names in the class must match the keys |
| 3346 | + * in the JSON string. |
| 3347 | + * |
| 3348 | + * @param object JSONObject of internal class |
| 3349 | + * @param clazz the class of the object to be returned |
| 3350 | + * @param <T> the type of the object |
| 3351 | + * @return an instance of type T with fields populated from the JSON string |
| 3352 | + */ |
| 3353 | + private <T> T fromJson(JSONObject object, Class<T> clazz) { |
| 3354 | + return object.fromJson(clazz); |
| 3355 | + } |
3210 | 3356 | } |
0 commit comments