/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
******************************************************************************/
package com.badlogic.gdx.utils;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.JsonWriter.OutputType;
import com.badlogic.gdx.utils.ObjectMap.Entry;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Reads/writes Java objects to/from JSON, automatically.
*
* @author Nathan Sweet
*/
public class Json {
private static final boolean debug = false;
private JsonWriter writer;
private String typeName = "class";
private boolean usePrototypes = true;
private OutputType outputType;
private final ObjectMap<Class, ObjectMap<String, FieldMetadata>> typeToFields = new ObjectMap();
private final ObjectMap<String, Class> tagToClass = new ObjectMap();
private final ObjectMap<Class, String> classToTag = new ObjectMap();
private final ObjectMap<Class, Serializer> classToSerializer = new ObjectMap();
private final ObjectMap<Class, Object[]> classToDefaultValues = new ObjectMap();
private boolean ignoreUnknownFields;
public Json() {
outputType = OutputType.minimal;
}
public Json(OutputType outputType) {
this.outputType = outputType;
}
public void setIgnoreUnknownFields(boolean ignoreUnknownFields) {
this.ignoreUnknownFields = ignoreUnknownFields;
}
public void setOutputType(OutputType outputType) {
this.outputType = outputType;
}
public void addClassTag(String tag, Class type) {
tagToClass.put(tag, type);
classToTag.put(type, tag);
}
public Class getClass(String tag) {
Class type = tagToClass.get(tag);
if (type != null)
return type;
try {
return Class.forName(tag);
} catch (ClassNotFoundException ex) {
throw new SerializationException(ex);
}
}
public String getTag(Class type) {
String tag = classToTag.get(type);
if (tag != null)
return tag;
return type.getName();
}
/**
* Sets the name of the JSON field to store the Java class name or class tag when required to avoid ambiguity during
* deserialization. Set to null to never output this information, but be warned that deserialization may fail.
*/
public void setTypeName(String typeName) {
this.typeName = typeName;
}
public <T> void setSerializer(Class<T> type, Serializer<T> serializer) {
classToSerializer.put(type, serializer);
}
public <T> Serializer<T> getSerializer(Class<T> type) {
return classToSerializer.get(type);
}
public void setUsePrototypes(boolean usePrototypes) {
this.usePrototypes = usePrototypes;
}
public void setElementType(Class type, String fieldName, Class elementType) {
ObjectMap<String, FieldMetadata> fields = typeToFields.get(type);
if (fields == null)
fields = cacheFields(type);
FieldMetadata metadata = fields.get(fieldName);
if (metadata == null)
throw new SerializationException("Field not found: " + fieldName + " (" + type.getName() + ")");
metadata.elementType = elementType;
}
private ObjectMap<String, FieldMetadata> cacheFields(Class type) {
ArrayList<Field> allFields = new ArrayList();
Class nextClass = type;
while (nextClass != Object.class) {
Collections.addAll(allFields, nextClass.getDeclaredFields());
nextClass = nextClass.getSuperclass();
}
ObjectMap<String, FieldMetadata> nameToField = new ObjectMap();
for (int i = 0, n = allFields.size(); i < n; i++) {
Field field = allFields.get(i);
int modifiers = field.getModifiers();
if (Modifier.isTransient(modifiers))
continue;
if (Modifier.isStatic(modifiers))
continue;
if (field.isSynthetic())
continue;
if (!field.isAccessible()) {
try {
field.setAccessible(true);
} catch (AccessControlException ex) {
continue;
}
}
nameToField.put(field.getName(), new FieldMetadata(field));
}
typeToFields.put(type, nameToField);
return nameToField;
}
public String toJson(Object object) {
return toJson(object, object == null ? null : object.getClass(), (Class) null);
}
public String toJson(Object object, Class knownType) {
return toJson(object, knownType, (Class) null);
}
/**
* @param knownType
* May be null if the type is unknown.
* @param elementType
* May be null if the type is unknown.
*/
public String toJson(Object object, Class knownType, Class elementType) {
StringWriter buffer = new StringWriter();
toJson(object, knownType, elementType, buffer);
return buffer.toString();
}
public void toJson(Object object, FileHandle file) {
toJson(object, object == null ? null : object.getClass(), null, file);
}
/**
* @param knownType
* May be null if the type is unknown.
*/
public void toJson(Object object, Class knownType, FileHandle file) {
toJson(object, knownType, null, file);
}
/**
* @param knownType
* May be null if the type is unknown.
* @param elementType
* May be null if the type is unknown.
*/
public void toJson(Object object, Class knownType, Class elementType, FileHandle file) {
Writer writer = null;
try {
writer = file.writer(false);
toJson(object, knownType, elementType, writer);
} catch (Exception ex) {
throw new SerializationException("Error writing file: " + file, ex);
} finally {
try {
if (writer != null)
writer.close();
} catch (IOException ignored) {
}
}
}
public void toJson(Object object, Writer writer) {
toJson(object, object == null ? null : object.getClass(), null, writer);
}
/**
* @param knownType
* May be null if the type is unknown.
*/
public void toJson(Object object, Class knownType, Writer writer) {
toJson(object, knownType, null, writer);
}
/**
* @param knownType
* May be null if the type is unknown.
* @param elementType
* May be null if the type is unknown.
*/
public void toJson(Object object, Class knownType, Class elementType, Writer writer) {
if (!(writer instanceof JsonWriter)) {
writer = new JsonWriter(writer);
}
((JsonWriter) writer).setOutputType(outputType);
this.writer = (JsonWriter) writer;
try {
writeValue(object, knownType, elementType);
} finally {
this.writer = null;
}
}
public void writeFields(Object object) {
Class type = object.getClass();
Object[] defaultValues = getDefaultValues(type);
ObjectMap<String, FieldMetadata> fields = typeToFields.get(type);
if (fields == null)
fields = cacheFields(type);
int i = 0;
for (FieldMetadata metadata : fields.values()) {
Field field = metadata.field;
try {
Object value = field.get(object);
if (defaultValues != null) {
Object defaultValue = defaultValues[i++];
if (value == null && defaultValue == null)
continue;
if (value != null && defaultValue != null && value.equals(defaultValue))
continue;
}
if (debug)
System.out.println("Writing field: " + field.getName() + " (" + type.getName() + ")");
writer.name(field.getName());
writeValue(value, field.getType(), metadata.elementType);
} catch (IllegalAccessException ex) {
throw new SerializationException("Error accessing field: " + field.getName() + " (" + type.getName()
+ ")", ex);
} catch (SerializationException ex) {
ex.addTrace(field + " (" + type.getName() + ")");
throw ex;
} catch (Exception runtimeEx) {
SerializationException ex = new SerializationException(runtimeEx);
ex.addTrace(field + " (" + type.getName() + ")");
throw ex;
}
}
}
private Object[] getDefaultValues(Class type) {
if (!usePrototypes)
return null;
if (classToDefaultValues.containsKey(type))
return classToDefaultValues.get(type);
Object object;
try {
object = newInstance(type);
} catch (Exception ex) {
classToDefaultValues.put(type, null);
return null;
}
ObjectMap<String, FieldMetadata> fields = typeToFields.get(type);
if (fields == null)
fields = cacheFields(type);
Object[] values = new Object[fields.size];
classToDefaultValues.put(type, values);
int i = 0;
for (FieldMetadata metadata : fields.values()) {
Field field = metadata.field;
try {
values[i++] = field.get(object);
} catch (IllegalAccessException ex) {
throw new SerializationException("Error accessing field: " + field.getName() + " (" + type.getName()
+ ")", ex);
} catch (SerializationException ex) {
ex.addTrace(field + " (" + type.getName() + ")");
throw ex;
} catch (RuntimeException runtimeEx) {
SerializationException ex = new SerializationException(runtimeEx);
ex.addTrace(field + " (" + type.getName() + ")");
throw ex;
}
}
return values;
}
public void writeField(Object object, String name) {
writeField(object, name, name, null);
}
/**
* @param elementType
* May be null if the type is unknown.
*/
public void writeField(Object object, String name, Class elementType) {
writeField(object, name, name, elementType);
}
public void writeField(Object object, String fieldName, String jsonName) {
writeField(object, fieldName, jsonName, null);
}
/**
* @param elementType
* May be null if the type is unknown.
*/
public void writeField(Object object, String fieldName, String jsonName, Class elementType) {
Class type = object.getClass();
ObjectMap<String, FieldMetadata> fields = typeToFields.get(type);
if (fields == null)
fields = cacheFields(type);
FieldMetadata metadata = fields.get(fieldName);
if (metadata == null)
throw new SerializationException("Field not found: " + fieldName + " (" + type.getName() + ")");
Field field = metadata.field;
if (elementType == null)
elementType = metadata.elementType;
try {
if (debug)
System.out.println("Writing field: " + field.getName() + " (" + type.getName() + ")");
writer.name(jsonName);
writeValue(field.get(object), field.getType(), elementType);
} catch (IllegalAccessException ex) {
throw new SerializationException("Error accessing field: " + field.getName() + " (" + type.getName() + ")",
ex);
} catch (SerializationException ex) {
ex.addTrace(field + " (" + type.getName() + ")");
throw ex;
} catch (Exception runtimeEx) {
SerializationException ex = new SerializationException(runtimeEx);
ex.addTrace(field + " (" + type.getName() + ")");
throw ex;
}
}
/**
* @param value
* May be null.
*/
public void writeValue(String name, Object value) {
try {
writer.name(name);
} catch (IOException ex) {
throw new SerializationException(ex);
}
if (value == null)
writeValue(value, null, null);
else
writeValue(value, value.getClass(), null);
}
/**
* @param value
* May be null.
* @param knownType
* May be null if the type is unknown.
*/
public void writeValue(String name, Object value, Class knownType) {
try {
writer.name(name);
} catch (IOException ex) {
throw new SerializationException(ex);
}
writeValue(value, knownType, null);
}
/**
* @param value
* May be null.
* @param knownType
* May be null if the type is unknown.
* @param elementType
* May be null if the type is unknown.
*/
public void writeValue(String name, Object value, Class knownType, Class elementType) {
try {
writer.name(name);
} catch (IOException ex) {
throw new SerializationException(ex);
}
writeValue(value, knownType, elementType);
}
/**
* @param value
* May be null.
*/
public void writeValue(Object value) {
if (value == null)
writeValue(value, null, null);
else
writeValue(value, value.getClass(), null);
}
/**
* @param value
* May be null.
* @param knownType
* May be null if the type is unknown.
*/
public void writeValue(Object value, Class knownType) {
writeValue(value, knownType, null);
}
/**
* @param value
* May be null.
* @param knownType
* May be null if the type is unknown.
* @param elementType
* May be null if the type is unknown.
*/
public void writeValue(Object value, Class knownType, Class elementType) {
try {
if (value == null) {
writer.value(null);
return;
}
Class actualType = value.getClass();
if (actualType.isPrimitive() || actualType == String.class || actualType == Integer.class
|| actualType == Boolean.class || actualType == Float.class || actualType == Long.class
|| actualType == Double.class || actualType == Short.class || actualType == Byte.class
|| actualType == Character.class) {
writer.value(value);
return;
}
if (value instanceof Serializable) {
writeObjectStart(actualType, knownType);
((Serializable) value).write(this);
writeObjectEnd();
return;
}
Serializer serializer = classToSerializer.get(actualType);
if (serializer != null) {
serializer.write(this, value, knownType);
return;
}
if (value instanceof Array) {
if (knownType != null && actualType != knownType)
throw new SerializationException(
"Serialization of an Array other than the known type is not supported.\n" + "Known type: "
+ knownType + "\nActual type: " + actualType);
writeArrayStart();
Array array = (Array) value;
for (int i = 0, n = array.size; i < n; i++)
writeValue(array.get(i), elementType, null);
writeArrayEnd();
return;
}
if (value instanceof Collection) {
if (knownType != null && actualType != knownType && actualType != ArrayList.class)
throw new SerializationException(
"Serialization of a Collection other than the known type is not supported.\n"
+ "Known type: " + knownType + "\nActual type: " + actualType);
writeArrayStart();
for (Object item : (Collection) value)
writeValue(item, elementType, null);
writeArrayEnd();
return;
}
if (actualType.isArray()) {
if (elementType == null)
elementType = actualType.getComponentType();
int length = java.lang.reflect.Array.getLength(value);
writeArrayStart();
for (int i = 0; i < length; i++)
writeValue(java.lang.reflect.Array.get(value, i), elementType, null);
writeArrayEnd();
return;
}
if (value instanceof OrderedMap) {
if (knownType == null)
knownType = OrderedMap.class;
writeObjectStart(actualType, knownType);
OrderedMap map = (OrderedMap) value;
for (Object key : map.orderedKeys()) {
writer.name(convertToString(key));
writeValue(map.get(key), elementType, null);
}
writeObjectEnd();
return;
}
if (value instanceof ArrayMap) {
if (knownType == null)
knownType = ArrayMap.class;
writeObjectStart(actualType, knownType);
ArrayMap map = (ArrayMap) value;
for (int i = 0, n = map.size; i < n; i++) {
writer.name(convertToString(map.keys[i]));
writeValue(map.values[i], elementType, null);
}
writeObjectEnd();
return;
}
if (value instanceof ObjectMap) {
if (knownType == null)
knownType = OrderedMap.class;
writeObjectStart(actualType, knownType);
for (Entry entry : ((ObjectMap<?, ?>) value).entries()) {
writer.name(convertToString(entry.key));
writeValue(entry.value, elementType, null);
}
writeObjectEnd();
return;
}
if (value instanceof Map) {
if (knownType == null)
knownType = OrderedMap.class;
writeObjectStart(actualType, knownType);
for (Map.Entry entry : ((Map<?, ?>) value).entrySet()) {
writer.name(convertToString(entry.getKey()));
writeValue(entry.getValue(), elementType, null);
}
writeObjectEnd();
return;
}
if (actualType.isEnum()) {
writer.value(value);
return;
}
writeObjectStart(actualType, knownType);
writeFields(value);
writeObjectEnd();
} catch (IOException ex) {
throw new SerializationException(ex);
}
}
public void writeObjectStart(String name) {
try {
writer.name(name);
} catch (IOException ex) {
throw new SerializationException(ex);
}
writeObjectStart();
}
/**
* @param knownType
* May be null if the type is unknown.
*/
public void writeObjectStart(String name, Class actualType, Class knownType) {
try {
writer.name(name);
} catch (IOException ex) {
throw new SerializationException(ex);
}
writeObjectStart(actualType, knownType);
}
public void writeObjectStart() {
try {
writer.object();
} catch (IOException ex) {
throw new SerializationException(ex);
}
}
/**
* @param knownType
* May be null if the type is unknown.
*/
public void writeObjectStart(Class actualType, Class knownType) {
try {
writer.object();
} catch (IOException ex) {
throw new SerializationException(ex);
}
if (knownType == null || knownType != actualType)
writeType(actualType);
}
public void writeObjectEnd() {
try {
writer.pop();
} catch (IOException ex) {
throw new SerializationException(ex);
}
}
public void writeArrayStart(String name) {
try {
writer.name(name);
writer.array();
} catch (IOException ex) {
throw new SerializationException(ex);
}
}
public void writeArrayStart() {
try {
writer.array();
} catch (IOException ex) {
throw new SerializationException(ex);
}
}
public void writeArrayEnd() {
try {
writer.pop();
} catch (IOException ex) {
throw new SerializationException(ex);
}
}
public void writeType(Class type) {
if (typeName == null)
return;
String className = classToTag.get(type);
if (className == null)
className = type.getName();
try {
writer.set(typeName, className);
} catch (IOException ex) {
throw new SerializationException(ex);
}
if (debug)
System.out.println("Writing type: " + type.getName());
}
/**
* @param type
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T fromJson(Class<T> type, Reader reader) {
return (T) readValue(type, null, new JsonReader().parse(reader));
}
/**
* @param type
* May be null if the type is unknown.
* @param elementType
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T fromJson(Class<T> type, Class elementType, Reader reader) {
return (T) readValue(type, elementType, new JsonReader().parse(reader));
}
/**
* @param type
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T fromJson(Class<T> type, InputStream input) {
return (T) readValue(type, null, new JsonReader().parse(input));
}
/**
* @param type
* May be null if the type is unknown.
* @param elementType
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T fromJson(Class<T> type, Class elementType, InputStream input) {
return (T) readValue(type, elementType, new JsonReader().parse(input));
}
/**
* @param type
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T fromJson(Class<T> type, FileHandle file) {
try {
return (T) readValue(type, null, new JsonReader().parse(file));
} catch (Exception ex) {
throw new SerializationException("Error reading file: " + file, ex);
}
}
/**
* @param type
* May be null if the type is unknown.
* @param elementType
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T fromJson(Class<T> type, Class elementType, FileHandle file) {
try {
return (T) readValue(type, elementType, new JsonReader().parse(file));
} catch (Exception ex) {
throw new SerializationException("Error reading file: " + file, ex);
}
}
/**
* @param type
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T fromJson(Class<T> type, char[] data, int offset, int length) {
return (T) readValue(type, null, new JsonReader().parse(data, offset, length));
}
/**
* @param type
* May be null if the type is unknown.
* @param elementType
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T fromJson(Class<T> type, Class elementType, char[] data, int offset, int length) {
return (T) readValue(type, elementType, new JsonReader().parse(data, offset, length));
}
/**
* @param type
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T fromJson(Class<T> type, String json) {
return (T) readValue(type, null, new JsonReader().parse(json));
}
/**
* @param type
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T fromJson(Class<T> type, Class elementType, String json) {
return (T) readValue(type, elementType, new JsonReader().parse(json));
}
public void readField(Object object, String name, Object jsonData) {
readField(object, name, name, null, jsonData);
}
public void readField(Object object, String name, Class elementType, Object jsonData) {
readField(object, name, name, elementType, jsonData);
}
public void readField(Object object, String fieldName, String jsonName, Object jsonData) {
readField(object, fieldName, jsonName, null, jsonData);
}
/**
* @param elementType
* May be null if the type is unknown.
*/
public void readField(Object object, String fieldName, String jsonName, Class elementType, Object jsonData) {
OrderedMap jsonMap = (OrderedMap) jsonData;
Class type = object.getClass();
ObjectMap<String, FieldMetadata> fields = typeToFields.get(type);
if (fields == null)
fields = cacheFields(type);
FieldMetadata metadata = fields.get(fieldName);
if (metadata == null)
throw new SerializationException("Field not found: " + fieldName + " (" + type.getName() + ")");
Field field = metadata.field;
Object jsonValue = jsonMap.get(jsonName);
if (jsonValue == null)
return;
if (elementType == null)
elementType = metadata.elementType;
try {
field.set(object, readValue(field.getType(), elementType, jsonValue));
} catch (IllegalAccessException ex) {
throw new SerializationException("Error accessing field: " + field.getName() + " (" + type.getName() + ")",
ex);
} catch (SerializationException ex) {
ex.addTrace(field.getName() + " (" + type.getName() + ")");
throw ex;
} catch (RuntimeException runtimeEx) {
SerializationException ex = new SerializationException(runtimeEx);
ex.addTrace(field.getName() + " (" + type.getName() + ")");
throw ex;
}
}
public void readFields(Object object, Object jsonData) {
OrderedMap<String, Object> jsonMap = (OrderedMap) jsonData;
Class type = object.getClass();
ObjectMap<String, FieldMetadata> fields = typeToFields.get(type);
if (fields == null)
fields = cacheFields(type);
for (Entry<String, Object> entry : jsonMap.entries()) {
FieldMetadata metadata = fields.get(entry.key);
if (metadata == null) {
if (ignoreUnknownFields) {
if (debug)
System.out.println("Ignoring unknown field: " + entry.key + " (" + type.getName() + ")");
continue;
} else
throw new SerializationException("Field not found: " + entry.key + " (" + type.getName() + ")");
}
Field field = metadata.field;
if (entry.value == null)
continue;
try {
field.set(object, readValue(field.getType(), metadata.elementType, entry.value));
} catch (IllegalAccessException ex) {
throw new SerializationException("Error accessing field: " + field.getName() + " (" + type.getName()
+ ")", ex);
} catch (SerializationException ex) {
ex.addTrace(field.getName() + " (" + type.getName() + ")");
throw ex;
} catch (RuntimeException runtimeEx) {
SerializationException ex = new SerializationException(runtimeEx);
ex.addTrace(field.getName() + " (" + type.getName() + ")");
throw ex;
}
}
}
/**
* @param type
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T readValue(String name, Class<T> type, Object jsonData) {
OrderedMap jsonMap = (OrderedMap) jsonData;
return (T) readValue(type, null, jsonMap.get(name));
}
/**
* @param type
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T readValue(String name, Class<T> type, T defaultValue, Object jsonData) {
OrderedMap jsonMap = (OrderedMap) jsonData;
Object jsonValue = jsonMap.get(name);
if (jsonValue == null)
return defaultValue;
return (T) readValue(type, null, jsonValue);
}
/**
* @param type
* May be null if the type is unknown.
* @param elementType
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T readValue(String name, Class<T> type, Class elementType, Object jsonData) {
OrderedMap jsonMap = (OrderedMap) jsonData;
return (T) readValue(type, elementType, jsonMap.get(name));
}
/**
* @param type
* May be null if the type is unknown.
* @param elementType
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T readValue(String name, Class<T> type, Class elementType, T defaultValue, Object jsonData) {
OrderedMap jsonMap = (OrderedMap) jsonData;
Object jsonValue = jsonMap.get(name);
if (jsonValue == null)
return defaultValue;
return (T) readValue(type, elementType, jsonValue);
}
/**
* @param type
* May be null if the type is unknown.
* @param elementType
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T readValue(Class<T> type, Class elementType, T defaultValue, Object jsonData) {
return (T) readValue(type, elementType, jsonData);
}
/**
* @param type
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T readValue(Class<T> type, Object jsonData) {
return (T) readValue(type, null, jsonData);
}
/**
* @param type
* May be null if the type is unknown.
* @param elementType
* May be null if the type is unknown.
* @return May be null.
*/
public <T> T readValue(Class<T> type, Class elementType, Object jsonData) {
if (jsonData == null)
return null;
if (jsonData instanceof OrderedMap) {
OrderedMap<String, Object> jsonMap = (OrderedMap) jsonData;
String className = typeName == null ? null : (String) jsonMap.remove(typeName);
if (className != null) {
try {
type = (Class<T>) Class.forName(className);
} catch (ClassNotFoundException ex) {
type = tagToClass.get(className);
if (type == null)
throw new SerializationException(ex);
}
}
Object object;
if (type != null) {
Serializer serializer = classToSerializer.get(type);
if (serializer != null)
return (T) serializer.read(this, jsonMap, type);
object = newInstance(type);
if (object instanceof Serializable) {
((Serializable) object).read(this, jsonMap);
return (T) object;
}
if (object instanceof HashMap) {
HashMap result = (HashMap) object;
for (Entry entry : jsonMap.entries())
result.put(entry.key, readValue(elementType, null, entry.value));
return (T) result;
}
} else
object = new OrderedMap();
if (object instanceof ObjectMap) {
ObjectMap result = (ObjectMap) object;
for (String key : jsonMap.orderedKeys())
result.put(key, readValue(elementType, null, jsonMap.get(key)));
return (T) result;
}
readFields(object, jsonMap);
return (T) object;
}
if (type != null) {
Serializer serializer = classToSerializer.get(type);
if (serializer != null)
return (T) serializer.read(this, jsonData, type);
}
if (jsonData instanceof Array) {
Array array = (Array) jsonData;
if (type == null || Array.class.isAssignableFrom(type)) {
Array newArray = type == null ? new Array() : (Array) newInstance(type);
newArray.ensureCapacity(array.size);
for (int i = 0, n = array.size; i < n; i++)
newArray.add(readValue(elementType, null, array.get(i)));
return (T) newArray;
}
if (ArrayList.class.isAssignableFrom(type)) {
ArrayList newArray = type == null ? new ArrayList() : (ArrayList) newInstance(type);
newArray.ensureCapacity(array.size);
for (int i = 0, n = array.size; i < n; i++)
newArray.add(readValue(elementType, null, array.get(i)));
return (T) newArray;
}
if (type.isArray()) {
Class componentType = type.getComponentType();
if (elementType == null)
elementType = componentType;
Object newArray = java.lang.reflect.Array.newInstance(componentType, array.size);
for (int i = 0, n = array.size; i < n; i++)
java.lang.reflect.Array.set(newArray, i, readValue(elementType, null, array.get(i)));
return (T) newArray;
}
throw new SerializationException("Unable to convert value to required type: " + jsonData + " ("
+ type.getName() + ")");
}
if (jsonData instanceof Float) {
Float floatValue = (Float) jsonData;
try {
if (type == null || type == float.class || type == Float.class)
return (T) (Float) floatValue;
if (type == int.class || type == Integer.class)
return (T) (Integer) floatValue.intValue();
if (type == long.class || type == Long.class)
return (T) (Long) floatValue.longValue();
if (type == double.class || type == Double.class)
return (T) (Double) floatValue.doubleValue();
if (type == short.class || type == Short.class)
return (T) (Short) floatValue.shortValue();
if (type == byte.class || type == Byte.class)
return (T) (Byte) floatValue.byteValue();
} catch (NumberFormatException ignored) {
}
jsonData = String.valueOf(jsonData);
}
if (jsonData instanceof Boolean)
jsonData = String.valueOf(jsonData);
if (jsonData instanceof String) {
String string = (String) jsonData;
if (type == null || type == String.class)
return (T) jsonData;
try {
if (type == int.class || type == Integer.class)
return (T) Integer.valueOf(string);
if (type == float.class || type == Float.class)
return (T) Float.valueOf(string);
if (type == long.class || type == Long.class)
return (T) Long.valueOf(string);
if (type == double.class || type == Double.class)
return (T) Double.valueOf(string);
if (type == short.class || type == Short.class)
return (T) Short.valueOf(string);
if (type == byte.class || type == Byte.class)
return (T) Byte.valueOf(string);
} catch (NumberFormatException ignored) {
}
if (type == boolean.class || type == Boolean.class)
return (T) Boolean.valueOf(string);
if (type == char.class || type == Character.class)
return (T) (Character) string.charAt(0);
if (type.isEnum()) {
Object[] constants = type.getEnumConstants();
for (int i = 0, n = constants.length; i < n; i++)
if (string.equals(constants[i].toString()))
return (T) constants[i];
}
if (type == CharSequence.class)
return (T) string;
throw new SerializationException("Unable to convert value to required type: " + jsonData + " ("
+ type.getName() + ")");
}
return null;
}
private String convertToString(Object object) {
if (object instanceof Class)
return ((Class) object).getName();
return String.valueOf(object);
}
private Object newInstance(Class type) {
try {
return type.newInstance();
} catch (Exception ex) {
try {
// Try a private constructor.
Constructor constructor = type.getDeclaredConstructor();
constructor.setAccessible(true);
return constructor.newInstance();
} catch (SecurityException ignored) {
} catch (NoSuchMethodException ignored) {
if (type.isArray())
throw new SerializationException("Encountered JSON object when expected array of type: "
+ type.getName(), ex);
else if (type.isMemberClass() && !Modifier.isStatic(type.getModifiers()))
throw new SerializationException("Class cannot be created (non-static member class): "
+ type.getName(), ex);
else
throw new SerializationException("Class cannot be created (missing no-arg constructor): "
+ type.getName(), ex);
} catch (Exception privateConstructorException) {
ex = privateConstructorException;
}
throw new SerializationException("Error constructing instance of class: " + type.getName(), ex);
}
}
public String prettyPrint(Object object) {
return prettyPrint(object, 0);
}
public String prettyPrint(String json) {
return prettyPrint(json, 0);
}
public String prettyPrint(Object object, int singleLineColumns) {
return prettyPrint(toJson(object), singleLineColumns);
}
public String prettyPrint(String json, int singleLineColumns) {
StringBuilder buffer = new StringBuilder(512);
prettyPrint(new JsonReader().parse(json), buffer, 0, singleLineColumns);
return buffer.toString();
}
private void prettyPrint(Object object, StringBuilder buffer, int indent, int singleLineColumns) {
if (object instanceof OrderedMap) {
OrderedMap<String, ?> map = (OrderedMap) object;
if (map.size == 0) {
buffer.append("{}");
} else {
boolean newLines = !isFlat(map);
int start = buffer.length();
outer: while (true) {
buffer.append(newLines ? "{\n" : "{ ");
int i = 0;
for (String key : map.orderedKeys()) {
if (newLines)
indent(indent, buffer);
buffer.append(outputType.quoteName(key));
buffer.append(": ");
prettyPrint(map.get(key), buffer, indent + 1, singleLineColumns);
if (i++ < map.size - 1)
buffer.append(",");
buffer.append(newLines ? '\n' : ' ');
if (!newLines && buffer.length() - start > singleLineColumns) {
buffer.setLength(start);
newLines = true;
continue outer;
}
}
break;
}
if (newLines)
indent(indent - 1, buffer);
buffer.append('}');
}
} else if (object instanceof Array) {
Array array = (Array) object;
if (array.size == 0) {
buffer.append("[]");
} else {
boolean newLines = !isFlat(array);
int start = buffer.length();
outer: while (true) {
buffer.append(newLines ? "[\n" : "[ ");
for (int i = 0, n = array.size; i < n; i++) {
if (newLines)
indent(indent, buffer);
prettyPrint(array.get(i), buffer, indent + 1, singleLineColumns);
if (i < array.size - 1)
buffer.append(",");
buffer.append(newLines ? '\n' : ' ');
if (!newLines && buffer.length() - start > singleLineColumns) {
buffer.setLength(start);
newLines = true;
continue outer;
}
}
break;
}
if (newLines)
indent(indent - 1, buffer);
buffer.append(']');
}
} else if (object instanceof String) {
buffer.append(outputType.quoteValue((String) object));
} else if (object instanceof Float) {
Float floatValue = (Float) object;
int intValue = floatValue.intValue();
buffer.append(floatValue - intValue == 0 ? intValue : object);
} else if (object instanceof Boolean) {
buffer.append(object);
} else if (object == null) {
buffer.append("null");
} else
throw new SerializationException("Unknown object type: " + object.getClass());
}
static private boolean isFlat(ObjectMap<?, ?> map) {
for (Entry entry : map.entries()) {
if (entry.value instanceof ObjectMap)
return false;
if (entry.value instanceof Array)
return false;
}
return true;
}
static private boolean isFlat(Array array) {
for (int i = 0, n = array.size; i < n; i++) {
Object value = array.get(i);
if (value instanceof ObjectMap)
return false;
if (value instanceof Array)
return false;
}
return true;
}
static private void indent(int count, StringBuilder buffer) {
for (int i = 0; i < count; i++)
buffer.append('\t');
}
static private class FieldMetadata {
Field field;
Class elementType;
public FieldMetadata(Field field) {
this.field = field;
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
Type[] actualTypes = ((ParameterizedType) genericType).getActualTypeArguments();
if (actualTypes.length == 1) {
Type actualType = actualTypes[0];
if (actualType instanceof Class)
elementType = (Class) actualType;
else if (actualType instanceof ParameterizedType)
elementType = (Class) ((ParameterizedType) actualType).getRawType();
}
}
}
}
static public interface Serializer<T> {
public void write(Json json, T object, Class knownType);
public T read(Json json, Object jsonData, Class type);
}
static abstract public class ReadOnlySerializer<T> implements Serializer<T> {
public void write(Json json, T object, Class knownType) {
}
abstract public T read(Json json, Object jsonData, Class type);
}
static public interface Serializable {
public void write(Json json);
public void read(Json json, OrderedMap<String, Object> jsonData);
}
}