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