blob: eb2e0ad0cfd62499cf3169f178913eec35d2d436 [file] [log] [blame]
/*******************************************************************************
* 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 java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.JsonValue.PrettyPrintSettings;
import com.badlogic.gdx.utils.JsonWriter.OutputType;
import com.badlogic.gdx.utils.ObjectMap.Entry;
import com.badlogic.gdx.utils.OrderedMap.OrderedMapValues;
import com.badlogic.gdx.utils.reflect.ArrayReflection;
import com.badlogic.gdx.utils.reflect.ClassReflection;
import com.badlogic.gdx.utils.reflect.Constructor;
import com.badlogic.gdx.utils.reflect.Field;
import com.badlogic.gdx.utils.reflect.ReflectionException;
/** Reads/writes Java objects to/from JSON, automatically. See the wiki for usage:
* https://github.com/libgdx/libgdx/wiki/Reading-%26-writing-JSON
* @author Nathan Sweet */
public class Json {
static private final boolean debug = false;
private JsonWriter writer;
private String typeName = "class";
private boolean usePrototypes = true;
private OutputType outputType;
private boolean quoteLongValues;
private boolean ignoreUnknownFields;
private boolean enumNames = true;
private Serializer defaultSerializer;
private final ObjectMap<Class, OrderedMap<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 final Object[] equals1 = {null}, equals2 = {null};
public Json () {
outputType = OutputType.minimal;
}
public Json (OutputType outputType) {
this.outputType = outputType;
}
/** When true, fields in the JSON that are not found on the class will not throw a {@link SerializationException}. Default is
* false. */
public void setIgnoreUnknownFields (boolean ignoreUnknownFields) {
this.ignoreUnknownFields = ignoreUnknownFields;
}
/** @see JsonWriter#setOutputType(OutputType) */
public void setOutputType (OutputType outputType) {
this.outputType = outputType;
}
/** @see JsonWriter#setQuoteLongValues(boolean) */
public void setQuoteLongValues (boolean quoteLongValues) {
this.quoteLongValues = quoteLongValues;
}
/** When true, {@link Enum#name()} is used to write enum values. When false, {@link Enum#toString()} is used which may not be
* unique. Default is true. */
public void setEnumNames (boolean enumNames) {
this.enumNames = enumNames;
}
/** Sets a tag to use instead of the fully qualifier class name. This can make the JSON easier to read. */
public void addClassTag (String tag, Class type) {
tagToClass.put(tag, type);
classToTag.put(type, tag);
}
/** Returns the class for the specified tag, or null. */
public Class getClass (String tag) {
return tagToClass.get(tag);
}
/** Returns the tag for the specified class, or null. */
public String getTag (Class type) {
return classToTag.get(type);
}
/** 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. Default is
* "class". */
public void setTypeName (String typeName) {
this.typeName = typeName;
}
/** Sets the serializer to use when the type being deserialized is not known (null).
* @param defaultSerializer May be null. */
public void setDefaultSerializer (Serializer defaultSerializer) {
this.defaultSerializer = defaultSerializer;
}
/** Registers a serializer to use for the specified type instead of the default behavior of serializing all of an objects
* fields. */
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);
}
/** When true, field values that are identical to a newly constructed instance are not written. Default is true. */
public void setUsePrototypes (boolean usePrototypes) {
this.usePrototypes = usePrototypes;
}
/** Sets the type of elements in a collection. When the element type is known, the class for each element in the collection
* does not need to be written unless different from the element type. */
public void setElementType (Class type, String fieldName, Class elementType) {
ObjectMap<String, FieldMetadata> fields = getFields(type);
FieldMetadata metadata = fields.get(fieldName);
if (metadata == null) throw new SerializationException("Field not found: " + fieldName + " (" + type.getName() + ")");
metadata.elementType = elementType;
}
private OrderedMap<String, FieldMetadata> getFields (Class type) {
OrderedMap<String, FieldMetadata> fields = typeToFields.get(type);
if (fields != null) return fields;
Array<Class> classHierarchy = new Array();
Class nextClass = type;
while (nextClass != Object.class) {
classHierarchy.add(nextClass);
nextClass = nextClass.getSuperclass();
}
ArrayList<Field> allFields = new ArrayList();
for (int i = classHierarchy.size - 1; i >= 0; i--)
Collections.addAll(allFields, ClassReflection.getDeclaredFields(classHierarchy.get(i)));
OrderedMap<String, FieldMetadata> nameToField = new OrderedMap(allFields.size());
for (int i = 0, n = allFields.size(); i < n; i++) {
Field field = allFields.get(i);
if (field.isTransient()) continue;
if (field.isStatic()) 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, "UTF-8");
toJson(object, knownType, elementType, writer);
} catch (Exception ex) {
throw new SerializationException("Error writing file: " + file, ex);
} finally {
StreamUtils.closeQuietly(writer);
}
}
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) {
setWriter(writer);
try {
writeValue(object, knownType, elementType);
} finally {
StreamUtils.closeQuietly(this.writer);
this.writer = null;
}
}
/** Sets the writer where JSON output will be written. This is only necessary when not using the toJson methods. */
public void setWriter (Writer writer) {
if (!(writer instanceof JsonWriter)) writer = new JsonWriter(writer);
this.writer = (JsonWriter)writer;
this.writer.setOutputType(outputType);
this.writer.setQuoteLongValues(quoteLongValues);
}
public JsonWriter getWriter () {
return writer;
}
/** Writes all fields of the specified object to the current JSON object. */
public void writeFields (Object object) {
Class type = object.getClass();
Object[] defaultValues = getDefaultValues(type);
OrderedMap<String, FieldMetadata> fields = getFields(type);
int i = 0;
for (FieldMetadata metadata : new OrderedMapValues<FieldMetadata>(fields)) {
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) {
if (value.equals(defaultValue)) continue;
if (value.getClass().isArray() && defaultValue.getClass().isArray()) {
equals1[0] = value;
equals2[0] = defaultValue;
if (Arrays.deepEquals(equals1, equals2)) continue;
}
}
}
if (debug) System.out.println("Writing field: " + field.getName() + " (" + type.getName() + ")");
writer.name(field.getName());
writeValue(value, field.getType(), metadata.elementType);
} catch (ReflectionException 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 = getFields(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 (ReflectionException 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;
}
/** @see #writeField(Object, String, String, Class) */
public void writeField (Object object, String name) {
writeField(object, name, name, null);
}
/** @param elementType May be null if the type is unknown.
* @see #writeField(Object, String, String, Class) */
public void writeField (Object object, String name, Class elementType) {
writeField(object, name, name, elementType);
}
/** @see #writeField(Object, String, String, Class) */
public void writeField (Object object, String fieldName, String jsonName) {
writeField(object, fieldName, jsonName, null);
}
/** Writes the specified field to the current JSON object.
* @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 = getFields(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 (ReflectionException 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;
}
}
/** Writes the value as a field on the current JSON object, without writing the actual class.
* @param value May be null.
* @see #writeValue(String, Object, Class, Class) */
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);
}
/** Writes the value as a field on the current JSON object, writing the class of the object if it differs from the specified
* known type.
* @param value May be null.
* @param knownType May be null if the type is unknown.
* @see #writeValue(String, Object, Class, Class) */
public void writeValue (String name, Object value, Class knownType) {
try {
writer.name(name);
} catch (IOException ex) {
throw new SerializationException(ex);
}
writeValue(value, knownType, null);
}
/** Writes the value as a field on the current JSON object, writing the class of the object if it differs from the specified
* known type. The specified element type is used as the default type for collections.
* @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);
}
/** Writes the value, without writing the class of the object.
* @param value May be null. */
public void writeValue (Object value) {
if (value == null)
writeValue(value, null, null);
else
writeValue(value, value.getClass(), null);
}
/** Writes the value, writing the class of the object if it differs from the specified known type.
* @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);
}
/** Writes the value, writing the class of the object if it differs from the specified known type. The specified element type
* is used as the default type for collections.
* @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;
}
if ((knownType != null && knownType.isPrimitive()) || knownType == String.class || knownType == Integer.class
|| knownType == Boolean.class || knownType == Float.class || knownType == Long.class || knownType == Double.class
|| knownType == Short.class || knownType == Byte.class || knownType == Character.class) {
writer.value(value);
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) {
writeObjectStart(actualType, null);
writeValue("value", value);
writeObjectEnd();
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;
}
// JSON array special cases.
if (value instanceof Array) {
if (knownType != null && actualType != knownType && actualType != Array.class)
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 Queue) {
if (knownType != null && actualType != knownType && actualType != Queue.class)
throw new SerializationException("Serialization of a Queue other than the known type is not supported.\n"
+ "Known type: " + knownType + "\nActual type: " + actualType);
writeArrayStart();
Queue queue = (Queue)value;
for (int i = 0, n = queue.size; i < n; i++)
writeValue(queue.get(i), elementType, null);
writeArrayEnd();
return;
}
if (value instanceof Collection) {
if (typeName != null && actualType != ArrayList.class && (knownType == null || knownType != actualType)) {
writeObjectStart(actualType, knownType);
writeArrayStart("items");
for (Object item : (Collection)value)
writeValue(item, elementType, null);
writeArrayEnd();
writeObjectEnd();
} else {
writeArrayStart();
for (Object item : (Collection)value)
writeValue(item, elementType, null);
writeArrayEnd();
}
return;
}
if (actualType.isArray()) {
if (elementType == null) elementType = actualType.getComponentType();
int length = ArrayReflection.getLength(value);
writeArrayStart();
for (int i = 0; i < length; i++)
writeValue(ArrayReflection.get(value, i), elementType, null);
writeArrayEnd();
return;
}
// JSON object special cases.
if (value instanceof ObjectMap) {
if (knownType == null) knownType = ObjectMap.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 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 Map) {
if (knownType == null) knownType = HashMap.class;
writeObjectStart(actualType, knownType);
for (Map.Entry entry : ((Map<?, ?>)value).entrySet()) {
writer.name(convertToString(entry.getKey()));
writeValue(entry.getValue(), elementType, null);
}
writeObjectEnd();
return;
}
// Enum special case.
if (ClassReflection.isAssignableFrom(Enum.class, actualType)) {
if (typeName != null && (knownType == null || knownType != actualType)) {
// Ensures that enums with specific implementations (abstract logic) serialize correctly.
if (actualType.getEnumConstants() == null) actualType = actualType.getSuperclass();
writeObjectStart(actualType, null);
writer.name("value");
writer.value(convertToString((Enum)value));
writeObjectEnd();
} else {
writer.value(convertToString((Enum)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);
}
}
/** Starts writing an object, writing the actualType to a field if needed.
* @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 = getTag(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, JsonValue jsonData) {
readField(object, name, name, null, jsonData);
}
public void readField (Object object, String name, Class elementType, JsonValue jsonData) {
readField(object, name, name, elementType, jsonData);
}
public void readField (Object object, String fieldName, String jsonName, JsonValue 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, JsonValue jsonMap) {
Class type = object.getClass();
ObjectMap<String, FieldMetadata> fields = getFields(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;
readField(object, field, jsonName, elementType, jsonMap);
}
/** @param object May be null if the field is static.
* @param elementType May be null if the type is unknown. */
public void readField (Object object, Field field, String jsonName, Class elementType, JsonValue jsonMap) {
JsonValue jsonValue = jsonMap.get(jsonName);
if (jsonValue == null) return;
try {
field.set(object, readValue(field.getType(), elementType, jsonValue));
} catch (ReflectionException ex) {
throw new SerializationException(
"Error accessing field: " + field.getName() + " (" + field.getDeclaringClass().getName() + ")", ex);
} catch (SerializationException ex) {
ex.addTrace(field.getName() + " (" + field.getDeclaringClass().getName() + ")");
throw ex;
} catch (RuntimeException runtimeEx) {
SerializationException ex = new SerializationException(runtimeEx);
ex.addTrace(jsonValue.trace());
ex.addTrace(field.getName() + " (" + field.getDeclaringClass().getName() + ")");
throw ex;
}
}
public void readFields (Object object, JsonValue jsonMap) {
Class type = object.getClass();
ObjectMap<String, FieldMetadata> fields = getFields(type);
for (JsonValue child = jsonMap.child; child != null; child = child.next) {
FieldMetadata metadata = fields.get(child.name);
if (metadata == null) {
if (child.name.equals(typeName)) continue;
if (ignoreUnknownFields) {
if (debug) System.out.println("Ignoring unknown field: " + child.name + " (" + type.getName() + ")");
continue;
} else {
SerializationException ex = new SerializationException(
"Field not found: " + child.name + " (" + type.getName() + ")");
ex.addTrace(child.trace());
throw ex;
}
}
Field field = metadata.field;
try {
field.set(object, readValue(field.getType(), metadata.elementType, child));
} catch (ReflectionException 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(child.trace());
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, JsonValue jsonMap) {
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, JsonValue jsonMap) {
JsonValue 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, JsonValue jsonMap) {
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, JsonValue jsonMap) {
JsonValue jsonValue = jsonMap.get(name);
return (T)readValue(type, elementType, defaultValue, 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, JsonValue jsonData) {
if (jsonData == null) return defaultValue;
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, JsonValue 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, JsonValue jsonData) {
if (jsonData == null) return null;
if (jsonData.isObject()) {
String className = typeName == null ? null : jsonData.getString(typeName, null);
if (className != null) {
type = getClass(className);
if (type == null) {
try {
type = (Class<T>)ClassReflection.forName(className);
} catch (ReflectionException ex) {
throw new SerializationException(ex);
}
}
}
if (type == null) {
if (defaultSerializer != null) return (T)defaultSerializer.read(this, jsonData, type);
return (T)jsonData;
}
if (typeName != null && ClassReflection.isAssignableFrom(Collection.class, type)) {
// JSON object wrapper to specify type.
jsonData = jsonData.get("items");
} else {
Serializer serializer = classToSerializer.get(type);
if (serializer != null) return (T)serializer.read(this, jsonData, type);
if (type == String.class || type == Integer.class || type == Boolean.class || type == Float.class
|| type == Long.class || type == Double.class || type == Short.class || type == Byte.class
|| type == Character.class || ClassReflection.isAssignableFrom(Enum.class, type)) {
return readValue("value", type, jsonData);
}
Object object = newInstance(type);
if (object instanceof Serializable) {
((Serializable)object).read(this, jsonData);
return (T)object;
}
// JSON object special cases.
if (object instanceof ObjectMap) {
ObjectMap result = (ObjectMap)object;
for (JsonValue child = jsonData.child; child != null; child = child.next)
result.put(child.name, readValue(elementType, null, child));
return (T)result;
}
if (object instanceof ArrayMap) {
ArrayMap result = (ArrayMap)object;
for (JsonValue child = jsonData.child; child != null; child = child.next)
result.put(child.name, readValue(elementType, null, child));
return (T)result;
}
if (object instanceof Map) {
Map result = (Map)object;
for (JsonValue child = jsonData.child; child != null; child = child.next)
result.put(child.name, readValue(elementType, null, child));
return (T)result;
}
readFields(object, jsonData);
return (T)object;
}
}
if (type != null) {
Serializer serializer = classToSerializer.get(type);
if (serializer != null) return (T)serializer.read(this, jsonData, type);
}
if (jsonData.isArray()) {
// JSON array special cases.
if (type == null || type == Object.class) type = (Class<T>)Array.class;
if (ClassReflection.isAssignableFrom(Array.class, type)) {
Array result = type == Array.class ? new Array() : (Array)newInstance(type);
for (JsonValue child = jsonData.child; child != null; child = child.next)
result.add(readValue(elementType, null, child));
return (T)result;
}
if (ClassReflection.isAssignableFrom(Queue.class, type)) {
Queue result = type == Queue.class ? new Queue() : (Queue)newInstance(type);
for (JsonValue child = jsonData.child; child != null; child = child.next)
result.addLast(readValue(elementType, null, child));
return (T)result;
}
if (ClassReflection.isAssignableFrom(Collection.class, type)) {
Collection result = type.isInterface() ? new ArrayList() : (Collection)newInstance(type);
for (JsonValue child = jsonData.child; child != null; child = child.next)
result.add(readValue(elementType, null, child));
return (T)result;
}
if (type.isArray()) {
Class componentType = type.getComponentType();
if (elementType == null) elementType = componentType;
Object result = ArrayReflection.newInstance(componentType, jsonData.size);
int i = 0;
for (JsonValue child = jsonData.child; child != null; child = child.next)
ArrayReflection.set(result, i++, readValue(elementType, null, child));
return (T)result;
}
throw new SerializationException("Unable to convert value to required type: " + jsonData + " (" + type.getName() + ")");
}
if (jsonData.isNumber()) {
try {
if (type == null || type == float.class || type == Float.class) return (T)(Float)jsonData.asFloat();
if (type == int.class || type == Integer.class) return (T)(Integer)jsonData.asInt();
if (type == long.class || type == Long.class) return (T)(Long)jsonData.asLong();
if (type == double.class || type == Double.class) return (T)(Double)jsonData.asDouble();
if (type == String.class) return (T)jsonData.asString();
if (type == short.class || type == Short.class) return (T)(Short)jsonData.asShort();
if (type == byte.class || type == Byte.class) return (T)(Byte)jsonData.asByte();
} catch (NumberFormatException ignored) {
}
jsonData = new JsonValue(jsonData.asString());
}
if (jsonData.isBoolean()) {
try {
if (type == null || type == boolean.class || type == Boolean.class) return (T)(Boolean)jsonData.asBoolean();
} catch (NumberFormatException ignored) {
}
jsonData = new JsonValue(jsonData.asString());
}
if (jsonData.isString()) {
String string = jsonData.asString();
if (type == null || type == String.class) return (T)string;
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 (ClassReflection.isAssignableFrom(Enum.class, type)) {
Enum[] constants = (Enum[])type.getEnumConstants();
for (int i = 0, n = constants.length; i < n; i++) {
Enum e = constants[i];
if (string.equals(convertToString(e))) return (T)e;
}
}
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 (Enum e) {
return enumNames ? e.name() : e.toString();
}
private String convertToString (Object object) {
if (object instanceof Enum) return convertToString((Enum)object);
if (object instanceof Class) return ((Class)object).getName();
return String.valueOf(object);
}
protected Object newInstance (Class type) {
try {
return ClassReflection.newInstance(type);
} catch (Exception ex) {
try {
// Try a private constructor.
Constructor constructor = ClassReflection.getDeclaredConstructor(type);
constructor.setAccessible(true);
return constructor.newInstance();
} catch (SecurityException ignored) {
} catch (ReflectionException ignored) {
if (ClassReflection.isAssignableFrom(Enum.class, type)) {
if (type.getEnumConstants() == null) type = type.getSuperclass();
return type.getEnumConstants()[0];
}
if (type.isArray())
throw new SerializationException("Encountered JSON object when expected array of type: " + type.getName(), ex);
else if (ClassReflection.isMemberClass(type) && !ClassReflection.isStaticClass(type))
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) {
return new JsonReader().parse(json).prettyPrint(outputType, singleLineColumns);
}
public String prettyPrint (Object object, PrettyPrintSettings settings) {
return prettyPrint(toJson(object), settings);
}
public String prettyPrint (String json, PrettyPrintSettings settings) {
return new JsonReader().parse(json).prettyPrint(settings);
}
static private class FieldMetadata {
Field field;
Class elementType;
public FieldMetadata (Field field) {
this.field = field;
int index = (ClassReflection.isAssignableFrom(ObjectMap.class, field.getType())
|| ClassReflection.isAssignableFrom(Map.class, field.getType())) ? 1 : 0;
this.elementType = field.getElementType(index);
}
}
static public interface Serializer<T> {
public void write (Json json, T object, Class knownType);
public T read (Json json, JsonValue 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, JsonValue jsonData, Class type);
}
static public interface Serializable {
public void write (Json json);
public void read (Json json, JsonValue jsonData);
}
}