diff --git a/src/main/java/com/fathzer/util/TinyJackson.java b/src/main/java/com/fathzer/util/TinyJackson.java new file mode 100644 index 0000000..dd615f7 --- /dev/null +++ b/src/main/java/com/fathzer/util/TinyJackson.java @@ -0,0 +1,165 @@ +package com.fathzer.util; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** A simplified version of Jackson Databind that only works on beans (default constructor + set and get accessors). + *
Jackson is cool, very easy to use, but fat, very fat (more than 4MB). + *
Json from json.org is light, but has nothing to convert json to an object (except JSONObject). + *
This class, adds bean parsing to the org.json library. Additionally, it provides you with method to convert beans and arrays + * to JSONObject and JSONArray (json.org ones have strange naming convention: for instance an object's attribute named 'aChar' is transformed + * in a JSON attribute named 'AChar'. + *

Current limitations: + *
  • All attributes of bean should be present in JSON
  • + *
  • The following types are not supported as bean's attributes: byte, short
  • + * + */ +public class TinyJackson { + + private TinyJackson() { + super(); + } + + public static T toObject(JSONObject json, Class tClass) { + try { + final T result = tClass.getConstructor().newInstance(); + final Field[] fields = tClass.getDeclaredFields(); + for (Field field : fields) { + final Class attrClass = field.getType(); + final String name = field.getName(); + final Method method = tClass.getMethod(getSetMethodName(name), attrClass); + final Object value = getValue(json, attrClass, name); + method.invoke(result, value); + } + return result; + } catch (ReflectiveOperationException | IllegalArgumentException e) { + throw new JSONException(e); + } + } + + private static Object getValue(JSONObject json, Class attrClass, String name) { + if (attrClass.isArray()) { + return toArray(json.getJSONArray(name), attrClass.getComponentType()); + } else if (attrClass.equals(String.class)) { + return json.getString(name); + } else if (attrClass.equals(int.class)) { + return json.getInt(name); + } else if (attrClass.equals(long.class)) { + return json.getLong(name); + } else if (attrClass.equals(float.class)) { + return json.getFloat(name); + } else if (attrClass.equals(double.class)) { + return json.getDouble(name); + } else if (attrClass.equals(boolean.class)) { + return json.getBoolean(name); + } else if (attrClass.equals(char.class)) { + final Object obj = json.get(name); + if (obj instanceof Character c) { + return c.charValue(); + } else if (obj instanceof String string) { + if (string.length()!=1) { + throw new IllegalArgumentException(name+ "attribute length != 1"); + } + return string.charAt(0); + } else { + throw new IllegalArgumentException("Unexpected type "+obj.getClass()+" for char attribute "+name); + } + } else { + // java object + return toObject(json.getJSONObject(name), attrClass); + } + } + + private static Object getValue(JSONArray json, Class attrClass, int index) { + if (attrClass.isArray()) { + //TODO To be tested + return toArray(json.getJSONArray(index), attrClass.getComponentType()); + } else if (attrClass.equals(String.class)) { + return json.getString(index); + } else if (attrClass.equals(int.class)) { + return json.getInt(index); + } else if (attrClass.equals(long.class)) { + return json.getLong(index); + } else if (attrClass.equals(float.class)) { + return json.getFloat(index); + } else if (attrClass.equals(double.class)) { + return json.getDouble(index); + } else if (attrClass.equals(boolean.class)) { + return json.getBoolean(index); + } else if (attrClass.equals(char.class)) { + final String string = json.getString(index); + if (string.length()!=1) { + throw new IllegalArgumentException(index+ "attribute length != 1"); + } + return string.charAt(0); + } else { + // java object + return toObject(json.getJSONObject(index), attrClass); + } + } + + private static String getSetMethodName(String attrName) { + return "set"+attrName.substring(0,1).toUpperCase()+attrName.substring(1); + } + + private static String getGetMethodName(Field field) { + final String attrName = field.getName(); + final String prefix = field.getType().equals(boolean.class) ? "is" : "get"; + return prefix+attrName.substring(0,1).toUpperCase()+attrName.substring(1); + } + + @SuppressWarnings("unchecked") + public static T[] toArray(JSONArray json, Class toClass) { + T[] result = (T[]) Array.newInstance(toClass, json.length()); + for (int i=0;i tClass = obj.getClass(); + final Field[] fields = tClass.getDeclaredFields(); + for (Field field : fields) { + final Class attrClass = field.getType(); + final String name = field.getName(); + final Method method = tClass.getMethod(getGetMethodName(field)); + final Object attr = method.invoke(obj); + if (attrClass.equals(String.class) || attrClass.equals(boolean.class) || attrClass.equals(int.class) || attrClass.equals(long.class) + || attrClass.equals(float.class) || attrClass.equals(double.class) || attrClass.equals(char.class)) { + result.put(name, attr); + } else if (attrClass.isArray()) { + result.put(name, toJSONArray((Object[])attr)); + } else { + result.put(name, toJSONObject(attr)); + } + } + } catch (ReflectiveOperationException | IllegalArgumentException e) { + throw new JSONException(e); + } + return result; + } + + public static JSONArray toJSONArray(T[] array) { + final JSONArray result = new JSONArray(); + final Class elClass = array.getClass().componentType(); + for (T element : array) { + if (elClass.equals(String.class) || elClass.equals(boolean.class) || elClass.equals(int.class) || elClass.equals(long.class) + || elClass.equals(float.class) || elClass.equals(double.class) || elClass.equals(char.class)) { + result.put(element); + } else if (elClass.isArray()) { + result.put(toJSONArray((Object[])element)); + } else { + result.put(toJSONObject(element)); + } + } + return result; + } +} diff --git a/src/test/java/com/fathzer/util/TinyJacksonTest.java b/src/test/java/com/fathzer/util/TinyJacksonTest.java new file mode 100644 index 0000000..a64bd52 --- /dev/null +++ b/src/test/java/com/fathzer/util/TinyJacksonTest.java @@ -0,0 +1,50 @@ +package com.fathzer.util; + +import static org.junit.jupiter.api.Assertions.*; + +import org.json.JSONObject; +import org.junit.jupiter.api.Test; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +class TinyJacksonTest { + @NoArgsConstructor + @Getter + @Setter + @ToString + @AllArgsConstructor + @EqualsAndHashCode + public static class TestClass { + private String str; + private int anInt; + private long aLong; + private char aChar; + private String[] strs; + private OtherClass other; + } + + @AllArgsConstructor + @NoArgsConstructor + @Setter + @Getter + @ToString + @EqualsAndHashCode + public static class OtherClass { + private String str; + private double value; + } + + @Test + void test() { + final TestClass obj = new TestClass("x",1,2,'a',new String[] {"a","b"}, new OtherClass("c",2.0)); + final JSONObject json = TinyJackson.toJSONObject(obj); + final String jsonString = json.toString(); + assertEquals(obj, TinyJackson.toObject(json, TestClass.class)); + assertEquals(obj, TinyJackson.toObject(new JSONObject(jsonString), TestClass.class)); + } +}