com.badlogic.gdx.utils.Json.java Source code

Java tutorial

Introduction

Here is the source code for com.badlogic.gdx.utils.Json.java

Source

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

    public Class getClass(String tagOrClassName) {
        Class type = tagToClass.get(tagOrClassName);
        if (type != null)
            return type;
        try {
            return ClassReflection.forName(tagOrClassName);
        } catch (ReflectionException 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. 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;

        ArrayList<Field> allFields = new ArrayList();
        Class nextClass = type;
        while (nextClass != Object.class) {
            Collections.addAll(allFields, ClassReflection.getDeclaredFields(nextClass));
            nextClass = nextClass.getSuperclass();
        }

        OrderedMap<String, FieldMetadata> nameToField = new OrderedMap();
        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 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 = 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, 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;
        JsonValue jsonValue = jsonMap.get(jsonName);
        if (jsonValue == null)
            return;
        if (elementType == null)
            elementType = metadata.elementType;
        try {
            field.set(object, readValue(field.getType(), elementType, jsonValue));
        } 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(field.getName() + " (" + type.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 (ignoreUnknownFields) {
                    if (debug)
                        System.out.println("Ignoring unknown field: " + child.name() + " (" + type.getName() + ")");
                    continue;
                } else
                    throw new SerializationException(
                            "Field not found: " + child.name() + " (" + type.getName() + ")");
            }
            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(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);
        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, JsonValue 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, 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) {
                jsonData.remove(typeName);
                type = tagToClass.get(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 (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);
            }

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

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