|
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); |
|
} |
|
} |
|
} |