Skip to content

Instantly share code, notes, and snippets.

@f2prateek
Last active August 29, 2015 14:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save f2prateek/7de50ad5361cf6bc25d0 to your computer and use it in GitHub Desktop.
Save f2prateek/7de50ad5361cf6bc25d0 to your computer and use it in GitHub Desktop.
A mini gson implementation.

A reflection based JSON databinder. A 'mini-gson' if you will. You'd probably want to tweak it for your specific cases a bit as well. It could also be more efficient, e.g. avoid allocating iterators during iteration and using indexes instead, etc.

Most notably it doesn't support

  • TypeAdapters
  • Generic top level types
  • No arrays support
  • SerializedName annotation
  • Only supports List, Map fields (no subclasses)

It shouldn't be too hard to add support for the last two though. JsonReader and JsonWriter are available on Android Honeycomb and up.

import java.io.InputStream;
import java.io.OutputStream;
public interface Converter {
public <T> T fromJson(InputStream in, Class<T> clazz) throws ConversionException;
public <T> void toJson(OutputStream os, T object) throws ConversionException;
public class ConversionException extends Exception {
public ConversionException(Throwable throwable) {
super(throwable);
}
public ConversionException() {
}
}
}
import android.annotation.TargetApi;
import android.os.Build;
import android.util.JsonReader;
import android.util.JsonToken;
import android.util.JsonWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/** A class to generate json from POJOs. */
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class JsonConverter implements Converter {
@Override public <T> T fromJson(InputStream is, Class<T> clazz) throws ConversionException {
try {
JsonReader reader = new JsonReader(new InputStreamReader(is, "UTF-8"));
return (T) fromJson(reader, clazz);
} catch (Exception e) {
throw new ConversionException(e);
}
}
@Override public <T> void toJson(OutputStream os, T object) throws ConversionException {
JsonWriter writer = null;
try {
writer = new JsonWriter(new OutputStreamWriter(os, "UTF-8"));
toJson(object, writer);
writer.close();
} catch (Exception e) {
throw new ConversionException(e);
}
}
void toJson(Object object, JsonWriter writer) throws Exception {
if (object == null) {
writer.nullValue();
} else if (object instanceof String) {
writer.value((String) object);
} else if (object instanceof Number) {
writer.value((Number) object);
} else if (object instanceof Boolean) {
writer.value((Boolean) object);
} else if (object instanceof Enum) {
writer.value(String.valueOf(object));
} else if (object instanceof Collection) {
writer.beginArray();
Collection collection = (Collection) object;
if (collection.size() == 0) {
for (Object value : collection) {
toJson(value, writer);
}
}
writer.endArray();
} else if (object instanceof Map) {
writer.beginObject();
Map<?, ?> map = (Map) object;
for (Map.Entry<?, ?> entry : map.entrySet()) {
writer.name(String.valueOf(entry));
toJson(entry.getValue(), writer);
}
writer.endObject();
} else {
writer.beginObject();
List<Field> fields = getFields(object);
for (Field field : fields) {
writer.name(field.getName());
toJson(field.get(object), writer);
}
writer.endObject();
}
}
Object fromJson(JsonReader reader, Class<?> clazz) throws Exception {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
}
// Primitives
if (clazz == String.class) {
return reader.nextString();
} else if (clazz == int.class || clazz == Integer.class) {
return reader.nextInt();
} else if (clazz == short.class || clazz == Short.class) {
return Integer.valueOf(reader.nextInt()).shortValue();
} else if (clazz == double.class || clazz == Double.class) {
return reader.nextDouble();
} else if (clazz == float.class || clazz == Float.class) {
return Double.valueOf(reader.nextDouble()).floatValue();
} else if (clazz == boolean.class || clazz == Boolean.class) {
return reader.nextBoolean();
} else if (clazz == long.class || clazz == Long.class) {
return reader.nextLong();
} else if (Enum.class.isAssignableFrom(clazz)) {
try {
Method valuesMethod = clazz.getMethod("valueOf", String.class);
return valuesMethod.invoke(null, reader.nextString());
} catch (NoSuchMethodException e) {
throw new ConversionException(e);
} catch (IllegalAccessException e) {
throw new ConversionException(e);
} catch (InvocationTargetException e) {
throw new ConversionException(e);
}
} else if (clazz == char.class || clazz == Character.class) {
String string = reader.nextString();
if (string.length() != 1) {
throw new IllegalArgumentException("Expected char but got " + string);
}
return string.charAt(0);
}
if (clazz == List.class || clazz == Collection.class || clazz == Map.class) {
throw new UnsupportedOperationException("top level type may not be generic type.");
}
Object object = newInstance(clazz);
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
Field field = getField(object, name);
if (field == null) {
reader.skipValue();
continue;
}
Class<?> fieldType = field.getType();
if (fieldType == List.class || clazz == Collection.class) {
reader.beginArray();
List list = new ArrayList();
while (reader.hasNext()) {
Class<?> listType =
(Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
list.add(fromJson(reader, listType));
}
reader.endArray();
field.set(object, list);
} else if (fieldType == Map.class) {
reader.beginObject();
Map map = new LinkedHashMap();
while (reader.hasNext()) {
Type[] actualTypeArguments =
((ParameterizedType) field.getGenericType()).getActualTypeArguments();
if (actualTypeArguments[0] != String.class) {
throw new AssertionError(
"Map type must be keyed by string not " + actualTypeArguments[0]);
}
map.put(reader.nextName(), fromJson(reader, (Class<?>) actualTypeArguments[1]));
}
reader.endObject();
field.set(object, map);
} else {
field.set(object, fromJson(reader, fieldType));
}
}
reader.endObject();
return object;
}
private static <V> V newInstance(Class<V> c) throws Exception {
Constructor<V> declaredConstructor = c.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
return declaredConstructor.newInstance();
}
private static Field getField(Object target, String name) {
return getField(target.getClass(), name);
}
private static Field getField(Class<?> clazz, String name) {
try {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException e) {
Class<?> parentClass = clazz.getSuperclass();
if (parentClass != null) {
return getField(parentClass, name);
}
return null;
}
}
private static List<Field> getFields(Object object) {
List<Field> fields = new ArrayList<Field>();
getFields(object.getClass(), fields);
return fields;
}
private static void getFields(Class<?> clazz, List<Field> fieldList) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
fieldList.add(field);
}
Class<?> parentClass = clazz.getSuperclass();
if (parentClass != null) {
getFields(parentClass, fieldList);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment