diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..898962d --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +/[Ll]ibrary/ +/[Tt]emp/ +/[Oo]bj/ +/[Bb]uild/ +/[Bb]uilds/ +/Assets/AssetStoreTools* + +# Ignore Editor Tests for now +/Assets/Editor* + +# Visual Studio 2015 cache directory +/.vs/ + +# Autogenerated VS/MD/Consulo solution and project files +ExportedObj/ +.consulo/ +*.csproj +*.unityproj +*.sln +*.suo +*.tmp +*.user +*.userprefs +*.pidb +*.booproj +*.svd +*.pdb + +# Unity3D generated meta files +*.pidb.meta + +# Unity3D Generated File On Crash Reports +sysinfo.txt + +# Builds +*.apk \ No newline at end of file diff --git a/Assets/Plugins.meta b/Assets/Plugins.meta new file mode 100644 index 0000000..7561fb6 --- /dev/null +++ b/Assets/Plugins.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 6131a24f85f494c2babb4da56bf30ee1 +folderAsset: yes +timeCreated: 1501101584 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/UnityJSON.meta b/Assets/Plugins/UnityJSON.meta new file mode 100644 index 0000000..af85c0a --- /dev/null +++ b/Assets/Plugins/UnityJSON.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: a18ed8a5ad0da4df5add1e7429d0e5cf +folderAsset: yes +timeCreated: 1501101649 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/UnityJSON/Attributes.cs b/Assets/Plugins/UnityJSON/Attributes.cs new file mode 100644 index 0000000..8add72b --- /dev/null +++ b/Assets/Plugins/UnityJSON/Attributes.cs @@ -0,0 +1,323 @@ +using SimpleJSON; +using System; +using System.Collections.Generic; + +namespace UnityJSON +{ + /// + /// Defines the JSON serialization and deserialization options for a + /// field or a property. If the field or the property does not have this + /// attribute, the default options are used. For private fields and + /// properties, this attribute is mandatory. + /// + [AttributeUsage (AttributeTargets.Field | AttributeTargets.Property)] + public class JSONNodeAttribute : Attribute + { + private NodeOptions _options; + private string _key = null; + + public JSONNodeAttribute (NodeOptions options) : base () + { + _options = options; + } + + public JSONNodeAttribute () : base () + { + _options = NodeOptions.Default; + } + + /// + /// The custom key value for this field/property when serializing + /// or deserializing. If this value is null, the field/property + /// name is used instead. + /// + public string key { + get { return _key; } + set { _key = value == "" ? null : value; } + } + + /// + /// The serialization/deserialization options associated with this + /// field/property. + /// + /// The options. + public NodeOptions options { + get { return _options; } + } + } + + /// + /// Defines serialization/deserialization customization for enums. + /// If no attribute is assigned to the enum, the names of the members + /// are simply used as strings. + /// + [AttributeUsage (AttributeTargets.Enum)] + public class JSONEnumAttribute : Attribute + { + private bool _useIntegers = false; + private JSONEnumMemberFormating _format = JSONEnumMemberFormating.None; + private string _prefix; + private string _suffix; + + /// + /// When true the numeric values of the enum members are used + /// for serialization/deserialization. Defaults to false. + /// + public bool useIntegers { + get { return _useIntegers; } + set { _useIntegers = value; } + } + + /// + /// Applies a formatting to the member names of the enumeration before + /// serializing/deserializing. + /// + public JSONEnumMemberFormating format { + get { return _format; } + set { _format = value; } + } + + /// + /// Applies a prefix to the member names of the enumeration before + /// serializing/deserializing. The prefix is added after the + /// formatting is applied. + /// + public string prefix { + get { return _prefix; } + set { _prefix = value == "" ? null : value; } + } + + /// + /// Applies a suffix to the member names of the enumeration before + /// serializing/deserializing. The suffix is added after the + /// formatting is applied. + /// + public string suffix { + get { return _suffix; } + set { _suffix = value == "" ? null : value; } + } + } + + /// + /// Defines general serialization/deserialization options applied to the + /// custom class or struct. These options are not node-specific and are + /// applied every time that class/struct is used. If the class or the + /// struct does not have this attribute, the default options are used. + /// + [AttributeUsage (AttributeTargets.Class | AttributeTargets.Struct)] + public class JSONObjectAttribute : Attribute + { + private ObjectOptions _options; + + public JSONObjectAttribute (ObjectOptions options) : base () + { + _options = options; + } + + public JSONObjectAttribute () : base () + { + _options = ObjectOptions.Default; + } + + /// + /// The general serialization/deserialization options associated + /// with this class/struct. + /// + public ObjectOptions options { + get { return _options; } + } + } + + /// + /// Defines the field/property where the unknown keys for a class or + /// a struct can be deserialized. Can only be used together with a + /// field or property of type Dictionary. If there + /// are multiple fields/properties with this attribute, only the first + /// one is used. + /// + [AttributeUsage (AttributeTargets.Field | AttributeTargets.Property)] + public class JSONExtrasAttribute : Attribute + { + private NodeOptions _options; + + public JSONExtrasAttribute (NodeOptions options) : base () + { + _options = options; + } + + public JSONExtrasAttribute () : base () + { + _options = NodeOptions.Default; + } + + /// + /// The serialization/deserialization options associated with + /// this field/property. + /// + public NodeOptions options { + get { return _options; } + } + } + + /// + /// Restricts the deserialization types for a field/property of type + /// object. This can also be used to add custom types to the deserialization + /// process as per default only primitive types, and arrays and dictionaries + /// thereof are created. + /// + /// This attribute can be used with fields and properties of type object, + /// list/array of objects or a dictionary with value type object. For dictionaries, + /// the restriction is only applied to the value type. + /// + [AttributeUsage (AttributeTargets.Field | AttributeTargets.Property)] + public class RestrictTypeAttribute : Attribute + { + private ObjectTypes _types; + private Type[] _customTypes; + + public RestrictTypeAttribute (ObjectTypes types) : base () + { + _types = types; + } + + public RestrictTypeAttribute (ObjectTypes types, Type[] customTypes) : base () + { + if (customTypes == null) { + throw new ArgumentNullException ("customTypes"); + } + } + + /// + /// The types that are allowed for this field/property. + /// + public ObjectTypes types { + get { return _types; } + } + + /// + /// Custom types that can be deserialized for this object. ObjectTypes + /// must allow custom types or an exception is thrown. The order of the + /// types are important as they will be tried one by one by the + /// deserializer. + /// + public Type[] customTypes { + get { return _customTypes; } + set { + if (!_types.SupportsCustom ()) { + throw new ArgumentException ("Attribute does not support custom types."); + } + + List typeList = new List (); + HashSet typeSet = new HashSet (); + foreach (Type type in value) { + if (type != null + && !typeSet.Contains (type) + && Util.IsCustomType (type)) { + typeSet.Add (type); + typeList.Add (type); + } + } + + if (typeList.Count != 0) { + _customTypes = typeList.ToArray (); + } + } + } + } + + /// + /// Adapts the instantiated class. If the node has the given + /// key / value pair, then the referenced type is instantiated. + /// This can be used together with interfaces or abstract classes + /// to determine the final class to be instantiated. One class or + /// interface can have multiple conditional attributes. The conditions + /// are proved in the order they are given and the type of the first + /// fulfilled condition is used. + /// + [AttributeUsage ( + AttributeTargets.Class | + AttributeTargets.Interface, + AllowMultiple = true)] + public class ConditionalInstantiationAttribute : Attribute + { + private Type _reference; + private string _key; + private object _value; + + public ConditionalInstantiationAttribute ( + Type reference, + string key, + object value) : base () + { + if (reference == null) { + throw new ArgumentNullException ("reference"); + } + if (key == null) { + throw new ArgumentNullException ("key"); + } + if (value == null) { + throw new ArgumentNullException ("value"); + } + _reference = reference; + _key = key; + _value = value; + } + + /// + /// The reference type to be instantiated. + /// + public Type referenceType { + get { return _reference; } + } + + /// + /// The key for the key / value condition pair. + /// + public string key { + get { return _key; } + } + + /// + /// The value for the key / value condition pair. + /// + public object value { + get { return _value; } + } + + /// + /// Removes the key / value pair if the condition is + /// matched. This can be useful if the pair does not + /// contain any data for the class / struct in order to + /// prevent any unknown key errors. + /// + public bool removeKey { get; set; } + } + + /// + /// Adapts the default type to be instantiated for this class + /// or interface. This is checked after all of the conditional + /// attributes (see ConditionalInstantiationAttribute) fail. + /// + [AttributeUsage ( + AttributeTargets.Class | + AttributeTargets.Interface)] + public class DefaultInstantiationAttribute : Attribute + { + private Type _reference; + + public DefaultInstantiationAttribute (Type reference) : base () + { + if (reference == null) { + throw new ArgumentNullException ("reference"); + } + _reference = reference; + } + + /// + /// The reference type to be instantiated. + /// + public Type referenceType { + get { return _reference; } + } + } +} diff --git a/Assets/Plugins/UnityJSON/Attributes.cs.meta b/Assets/Plugins/UnityJSON/Attributes.cs.meta new file mode 100644 index 0000000..4db4e68 --- /dev/null +++ b/Assets/Plugins/UnityJSON/Attributes.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 870c19a136b8843269b388ffb07a95c5 +timeCreated: 1501102207 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/UnityJSON/Deserializer.cs b/Assets/Plugins/UnityJSON/Deserializer.cs new file mode 100644 index 0000000..31888af --- /dev/null +++ b/Assets/Plugins/UnityJSON/Deserializer.cs @@ -0,0 +1,1063 @@ +using SimpleJSON; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; + +namespace UnityJSON +{ + /// + /// An exception during the deserialization process. + /// + public class DeserializationException : Exception + { + public DeserializationException () : base () + { + } + + public DeserializationException (string message) : base (message) + { + } + } + + /// + /// Deserializes JSON string either into newly instantiated or + /// previously existing objects. + /// + public class Deserializer + { + private static Deserializer _default = new Deserializer (); + private static Deserializer _simple = _default; + + /// + /// The default deserializer to be used when no deserializer is given. + /// You can set this to your own default deserializer. Uses the + /// #Simple deserializer by default. + /// + public static Deserializer Default { + get { return _default; } + set { + if (value == null) { + throw new ArgumentNullException ("default deserializer"); + } + _simple = value; + } + } + + /// + /// The initial deserializer that is provided by the framework. + /// + public static Deserializer Simple { + get { return _simple; } + } + + /// + /// Tries to instantiate an object of a given type. This will be called + /// for all custom objects before trying to instantiate the object with a + /// default constructor. + /// + /// Subclasses should override this method to provide instantiated versions + /// for classes with constructors with arguments or interfaces or abstract + /// classes. The JSON node can be used to decide which class or struct to + /// instantiate. + /// + protected virtual bool TryInstantiate ( + JSONNode node, + Type type, + NodeOptions options, + out object instantiatedObject) + { + instantiatedObject = null; + return false; + } + + /// + /// Tries to deserialize the JSON node onto the given object. It is guaranteed + /// that the object is not null. This will be called before trying any other + /// deserialization method. Subclasses should override this method to perform + /// their own deserialization logic. + /// + protected virtual bool TryDeserializeOn ( + object obj, + JSONNode node, + NodeOptions options) + { + return false; + } + + /// + /// Deserializes the JSON string directly on the object. Throws an + /// ArgumentNullException of the object is null. + /// + public void DeserializeOn ( + object obj, + JSONNode node, + NodeOptions options = NodeOptions.Default) + { + if (obj == null) { + throw new ArgumentNullException ("obj"); + } + + if (TryDeserializeOn (obj, node, options)) { + return; + } + + Type type = obj.GetType (); + if (type.IsEnum) { + throw new ArgumentException ("Cannot deserialize on enums."); + } else if (type.IsPrimitive) { + throw new ArgumentException ("Cannot deserialize on primitive types."); + } else if (!node.IsObject) { + throw new ArgumentException ("Expected a JSON object, found " + node.Tag); + } + + _FeedCustom (obj, node, options); + } + + /// + /// Deserializes the JSON node to a new object of the requested type. This + /// will first call #TryInstantiate to create an object for the type, then + /// try the default constructor without arguments. If an object can be + /// instantiated, then first the IDeserializable.Deserialize method will + /// be used if the object implements the interface. If not, the framework + /// deserialization will be performed. + /// + /// JSON node to deserialize. + /// Requested type of the deserialized object. + /// Deserialization options for the node (optional). + public object Deserialize ( + JSONNode node, + Type type, + NodeOptions options = NodeOptions.Default) + { + return _Deserialize (node, type, options, ObjectTypes.JSON, null); + } + + /// + /// Deserializes a JSON node into a C# System.Object type. If no restrictions + /// are given, the deserialized types can be doubles, booleans, strings, and arrays + /// and dictionaries thereof. Restricted types can allow custom types to create + /// classes or structs instead of dictionaries. + /// + /// JSON node to deserialize. + /// Restricted types for the object. + /// Allowed custom types for the object. Restrictions + /// must allow custom types if not null. + /// Deserialization options. + public object DeserializeToObject ( + JSONNode node, + ObjectTypes restrictedTypes = ObjectTypes.JSON, + Type[] customTypes = null, + NodeOptions options = NodeOptions.Default) + { + if (node == null || node.IsNull) { + return null; + } + if (customTypes != null) { + if (!restrictedTypes.SupportsCustom ()) { + throw new ArgumentException ("Restrictions do not allow custom types."); + } + foreach (Type type in customTypes) { + if (!Util.IsCustomType (type)) { + throw new ArgumentException ("Unsupported custom type: " + type); + } + } + } + return _DeserializeToObject (node, options, restrictedTypes, customTypes); + } + + /// + /// Deserializes a JSON node into a System.Nullable object. + /// + public Nullable DeserializeToNullable ( + JSONNode node, + NodeOptions options = NodeOptions.Default) where T : struct + { + if (node == null || node.IsNull) { + return null; + } + return (Nullable) Deserialize (node, typeof(T), options); + } + + /// + /// Deserializes a JSON node into an integer. Throws an ArgumentNullException + /// if the node is null. Throws a CastException if the node does + /// not contain an integer. + /// + public int DeserializeToInt ( + JSONNode node, + NodeOptions options = NodeOptions.Default) + { + if (node == null) { + throw new ArgumentNullException ("node"); + } + return (int)_DeserializeToInt (node, options); + } + + /// + /// Deserializes a JSON node into an unsigned integer. Throws an ArgumentNullException + /// if the node is null. Throws a CastException if the node does + /// not contain an unsigned integer. + /// + public uint DeserializeToUInt ( + JSONNode node, + NodeOptions options = NodeOptions.Default) + { + if (node == null) { + throw new ArgumentNullException ("node"); + } + return (uint)_DeserializeToUInt (node, options); + } + + /// + /// Deserializes a JSON node into a byte. Throws an ArgumentNullException + /// if the node is null. Throws a CastException if the node does + /// not contain a byte. + /// + public byte DeserializeToByte ( + JSONNode node, + NodeOptions options = NodeOptions.Default) + { + if (node == null) { + throw new ArgumentNullException ("node"); + } + return (byte)_DeserializeToByte (node, options); + } + + /// + /// Deserializes a JSON node into a boolean. Throws an ArgumentNullException + /// if the node is null. Throws a CastException if the node does + /// not contain a boolean. + /// + public bool DeserializeToBool ( + JSONNode node, + NodeOptions options = NodeOptions.Default) + { + if (node == null) { + throw new ArgumentNullException ("node"); + } + return (bool)_DeserializeToBool (node, options); + } + + /// + /// Deserializes a JSON node into a float. Throws an ArgumentNullException + /// if the node is null. Throws a CastException if the node does + /// not contain a float. + /// + public float DeserializeToFloat ( + JSONNode node, + NodeOptions options = NodeOptions.Default) + { + if (node == null) { + throw new ArgumentNullException ("node"); + } + return (float)_DeserializeToFloat (node, options); + } + + /// + /// Deserializes a JSON node into a double. Throws an ArgumentNullException + /// if the node is null. Throws a CastException if the node does + /// not contain a double. + /// + public double DeserializeToDouble ( + JSONNode node, + NodeOptions options = NodeOptions.Default) + { + if (node == null) { + throw new ArgumentNullException ("node"); + } + return (double)_DeserializeToDouble (node, options); + } + + /// + /// Deserializes a JSON node into a long. Throws an ArgumentNullException + /// if the node is null. Throws a CastException if the node does + /// not contain a long. + /// + public long DeserializeToLong ( + JSONNode node, + NodeOptions options = NodeOptions.Default) + { + if (node == null) { + throw new ArgumentNullException ("node"); + } + return (long)_DeserializeToLong (node, options); + } + + /// + /// Deserializes a JSON node into a string. + /// + public string DeserializeToString ( + JSONNode node, + NodeOptions options = NodeOptions.Default) + { + if (node == null || node.IsNull) { + return null; + } + return _DeserializeToString (node, options); + } + + /// + /// Deserializes a JSON node into an enum. Throws an ArgumentNullException + /// if the node is null. Throws an ArgumentException if the generic + /// type T is not an enum. + /// + public T DeserializeToEnum ( + JSONNode node, + NodeOptions options = NodeOptions.Default) + { + if (node == null || node.IsNull) { + throw new ArgumentNullException ("node"); + } + if (!typeof(T).IsEnum) { + throw new ArgumentException ("Generic type is not an enum."); + } + return (T)_DeserializeToEnum (node, typeof(T), options); + } + + /// + /// Deserializes a JSON node into a generic list. + /// + public List DeserializeToList ( + JSONNode node, + NodeOptions options = NodeOptions.Default) + { + if (node == null || node.IsNull) { + return null; + } + var list = new List (); + _FeedList (list, node, typeof(T), options); + return list; + } + + /// + /// Deserializes a JSON node into a System.Object list. If no restrictions + /// are given, the deserialized types can be doubles, booleans, strings, and arrays + /// and dictionaries thereof. Restricted types can allow custom types to create + /// classes or structs instead of dictionaries. + /// + /// JSON node to deserialize. + /// Restricted types for the object. + /// Allowed custom types for the object. Restrictions + /// must allow custom types if not null. + /// Deserialization options. + public List DeserializeToObjectList ( + JSONNode node, + ObjectTypes restrictedTypes = ObjectTypes.JSON, + Type[] customTypes = null, + NodeOptions options = NodeOptions.Default) + { + if (node == null || node.IsNull) { + return null; + } + var list = new List (); + _FeedList (list, node, typeof(object), options, restrictedTypes, customTypes); + return list; + } + + /// + /// Deserializes a JSON node into a generic dictionary. + /// + public Dictionary DeserializeToDictionary ( + JSONNode node, + NodeOptions options = NodeOptions.Default) + { + if (node == null || node.IsNull) { + return null; + } + var dictionary = new Dictionary (); + _FeedDictionary (dictionary, node, typeof(K), typeof(V), options); + return dictionary; + } + + /// + /// Deserializes a JSON node into a dictionary with value type System.Object. If + /// no restrictions are given, the deserialized value types can be doubles, booleans, + /// strings, and arrays and dictionaries thereof. Restricted types can allow custom + /// types to create classes or structs instead of dictionaries. + /// + /// JSON node to deserialize. + /// Restricted types for the values. + /// Allowed custom types for the values. Restrictions + /// must allow custom types if not null. + /// Deserialization options. + public Dictionary DeserializeToObjectDictionary ( + JSONNode node, + ObjectTypes restrictedTypes = ObjectTypes.JSON, + Type[] customTypes = null, + NodeOptions options = NodeOptions.Default) + { + if (node == null || node.IsNull) { + return null; + } + var dictionary = new Dictionary (); + _FeedDictionary ( + dictionary, + node, + typeof(K), + typeof(object), + options, + restrictedTypes, + customTypes); + return dictionary; + } + + private object _Deserialize ( + JSONNode node, + Type type, + NodeOptions options, + ObjectTypes types, + Type[] customTypes) + { + if (node == null || node.IsNull) { + return null; + } + + object obj; + if (TryInstantiate (node, type, options, out obj)) { + DeserializeOn (obj, node, options); + return obj; + } + + if (type == typeof(object)) { + return _DeserializeToObject (node, options, types, customTypes); + } + + if (type.IsValueType) { + if (type.IsEnum) { + return _DeserializeToEnum (node, type, options); + } else if (type.IsPrimitive) { + return _DeserializeToPrimitive (node, type, options); + } + } else { + if (type == typeof(string)) { + return _DeserializeToString (node, options); + } else if (Nullable.GetUnderlyingType (type) != null) { + return _DeserializeToNullable (node, type, options); + } else if (typeof(IList).IsAssignableFrom (type)) { + return _DeserializeToIList (node, type, options, types, customTypes); + } else if (Util.IsDictionary (type)) { + return _DeserializeToIDictionary (node, type, options, types, customTypes); + } + } + return _DeserializeCustom (node, type, options); + } + + private object _Deserialize ( + JSONNode node, + Type type, + NodeOptions options, + MemberInfo memberInfo) + { + var typeAttribute = memberInfo == null + ? null : Util.GetAttribute (memberInfo); + ObjectTypes types = typeAttribute == null ? ObjectTypes.JSON : typeAttribute.types; + Type[] customTypes = typeAttribute == null ? null : typeAttribute.customTypes; + return _Deserialize (node, type, options, types, customTypes); + } + + private object _DeserializeToObject ( + JSONNode node, + NodeOptions options, + ObjectTypes restrictedTypes, + Type[] customTypes) + { + if (node.IsArray) { + if (!restrictedTypes.SupportsArray ()) { + return _HandleMismatch (options, "Arrays are not supported for object."); + } + return _DeserializeToArray ( + node, + typeof(object), + options, + restrictedTypes, + customTypes); + } else if (node.IsBoolean) { + if (!restrictedTypes.SupportsBool ()) { + return _HandleMismatch (options, "Bools are not supported for object."); + } + return node.AsBool; + } else if (node.IsNumber) { + if (!restrictedTypes.SupportsNumber ()) { + return _HandleMismatch (options, "Numbers are not supported for object."); + } + return node.AsDouble; + } else if (node.IsObject) { + if (restrictedTypes.SupportsCustom () && customTypes != null) { + foreach (Type customType in customTypes) { + try { + var obj = Deserialize (node, customType, NodeOptions.Default); + if (obj != null) { + return obj; + } + } catch (Exception) { + } + } + } + + if (!restrictedTypes.SupportsDictionary ()) { + return _HandleMismatch (options, "Dictionaries are not supported for object."); + } + return _DeserializeToGenericDictionary ( + node, + typeof(string), + typeof(object), + options, + restrictedTypes, + customTypes); + } else if (node.IsString) { + if (!restrictedTypes.SupportsString ()) { + return _HandleMismatch (options, "Strings are not supported for object."); + } + return _DeserializeToString (node, options); + } else { + return _HandleUnknown (options, "Unknown JSON node type " + node); + } + } + + private object _DeserializeToNullable (JSONNode node, Type nullableType, NodeOptions options) + { + Type underlyingType = Nullable.GetUnderlyingType (nullableType); + return Deserialize (node, underlyingType, options); + } + + private object _DeserializeToPrimitive (JSONNode node, Type type, NodeOptions options) + { + if (type == typeof(int)) { + return _DeserializeToInt (node, options); + } else if (type == typeof(byte)) { + return _DeserializeToByte (node, options); + } else if (type == typeof(long)) { + return _DeserializeToByte (node, options); + } else if (type == typeof(uint)) { + return _DeserializeToUInt (node, options); + } else if (type == typeof(bool)) { + return _DeserializeToBool (node, options); + } else if (type == typeof(float)) { + return _DeserializeToFloat (node, options); + } else if (type == typeof(double)) { + return _DeserializeToDouble (node, options); + } else { + return _HandleUnknown (options, "Unknown primitive type " + type); + } + } + + private string _DeserializeToString (JSONNode node, NodeOptions options) + { + if (!node.IsString) { + return _HandleMismatch (options, "Expected string, found: " + node) as string; + } else { + return node.Value; + } + } + + private object _DeserializeToInt (JSONNode node, NodeOptions options) + { + if (node.IsNumber) { + int value; + if (int.TryParse (node.Value, out value)) { + return value; + } + } + return _HandleMismatch (options, "Expected integer, found " + node); + } + + private object _DeserializeToUInt (JSONNode node, NodeOptions options) + { + if (node.IsNumber) { + uint value; + if (uint.TryParse (node.Value, out value)) { + return value; + } + } + return _HandleMismatch (options, "Expected unsigned integer, found " + node); + } + + private object _DeserializeToByte (JSONNode node, NodeOptions options) + { + if (node.IsNumber) { + byte value; + if (byte.TryParse (node.Value, out value)) { + return value; + } + } + return _HandleMismatch (options, "Expected byte, found " + node); + } + + private object _DeserializeToLong (JSONNode node, NodeOptions options) + { + if (node.IsNumber) { + long value; + if (long.TryParse (node.Value, out value)) { + return value; + } + } + return _HandleMismatch (options, "Expected long, found " + node); + } + + private object _DeserializeToFloat (JSONNode node, NodeOptions options) + { + if (node.IsNumber) { + return node.AsFloat; + } + return _HandleMismatch (options, "Expected float, found " + node); + } + + private object _DeserializeToDouble (JSONNode node, NodeOptions options) + { + if (node.IsNumber) { + return node.AsDouble; + } + return _HandleMismatch (options, "Expected double, found " + node); + } + + private object _DeserializeToBool (JSONNode node, NodeOptions options) + { + if (node.IsBoolean) { + return node.AsBool; + } + return _HandleMismatch (options, "Expected integer, found " + node); + } + + private object _DeserializeToEnum (JSONNode node, Type enumType, NodeOptions options) + { + Func handleError = () => _HandleMismatch ( + options, "Expected enum of type " + enumType + ", found: " + node); + + var enumAttribute = Util.GetAttribute (enumType); + if (enumAttribute != null && enumAttribute.useIntegers && node.IsNumber) { + try { + return Enum.ToObject (enumType, _DeserializeToInt (node, options)); + } catch (Exception) { + } + } else if (node.IsString) { + string value = node.Value; + if (enumAttribute != null) { + if (enumAttribute.prefix != null) { + if (!value.StartsWith (enumAttribute.prefix)) { + return handleError (); + } else { + value = value.Substring (enumAttribute.prefix.Length); + } + } + if (enumAttribute.suffix != null) { + if (!value.EndsWith (enumAttribute.suffix)) { + return handleError (); + } else { + value = value.Substring (0, value.Length - enumAttribute.suffix.Length); + } + } + } + try { + return Enum.Parse (enumType, value, true); + } catch (Exception) { + } + } + return handleError (); + } + + private IDictionary _DeserializeToIDictionary ( + JSONNode node, + Type dictionaryType, + NodeOptions options, + ObjectTypes types, + Type[] customTypes) + { + Type genericType = dictionaryType.IsGenericType ? (dictionaryType.IsGenericTypeDefinition + ? dictionaryType : dictionaryType.GetGenericTypeDefinition ()) : null; + if (dictionaryType == typeof(IDictionary)) { + return _DeserializeToGenericDictionary ( + node, + typeof(string), + typeof(object), + options, + types, + customTypes); + } else if (genericType == typeof(IDictionary<,>) || genericType == typeof(Dictionary<,>)) { + var args = dictionaryType.GetGenericArguments (); + return _DeserializeToGenericDictionary ( + node, + args [0], + args [1], + options, + types, + customTypes); + } else { + return _HandleUnknown (options, "Unknown dictionary type " + dictionaryType) as IDictionary; + } + } + + private IList _DeserializeToIList ( + JSONNode node, + Type listType, + NodeOptions options, + ObjectTypes types, + Type[] customTypes) + { + Type genericType = listType.IsGenericType ? (listType.IsGenericTypeDefinition + ? listType : listType.GetGenericTypeDefinition ()) : null; + if (listType == typeof(Array)) { + return _DeserializeToArray ( + node, + typeof(object), + options, + types, + customTypes); + } else if (listType.IsArray) { + return _DeserializeToArray ( + node, + listType.GetElementType (), + options, + types, + customTypes); + } else if (listType == typeof(IList)) { + return _DeserializeToGenericList ( + node, + typeof(object), + options, + types, + customTypes); + } else if (genericType == typeof(IList<>) || genericType == typeof(List<>)) { + return _DeserializeToGenericList ( + node, + listType.GetGenericArguments () [0], + options, + types, + customTypes); + } else { + return _HandleUnknown (options, "Unknown list type " + listType) as IList; + } + } + + private Array _DeserializeToArray ( + JSONNode node, + Type elementType, + NodeOptions options, + ObjectTypes types, + Type[] customTypes) + { + IList list = _DeserializeToGenericList ( + node, + elementType, + options, + types, + customTypes); + Array array = Array.CreateInstance (elementType, list.Count); + list.CopyTo (array, 0); + return array; + } + + private IList _DeserializeToGenericList ( + JSONNode node, + Type genericArgument, + NodeOptions options, + ObjectTypes types = ObjectTypes.JSON, + Type[] customTypes = null) + { + IList list = (IList)Activator.CreateInstance (typeof(List<>).MakeGenericType (genericArgument)); + _FeedList (list, node, genericArgument, options, types, customTypes); + return list; + } + + private void _FeedList ( + IList list, + JSONNode node, + Type genericArgument, + NodeOptions options, + ObjectTypes types = ObjectTypes.JSON, + Type[] customTypes = null) + { + if (node.IsArray) { + JSONArray array = node as JSONArray; + IEnumerator enumerator = array.GetEnumerator (); + while (enumerator.MoveNext ()) { + JSONNode child = (JSONNode)enumerator.Current; + // Throws an error if needed. + list.Add (_Deserialize ( + child, + genericArgument, + options & ~NodeOptions.ReplaceDeserialized, + types, + customTypes)); + } + } else { + _HandleMismatch (options, "Expected an array, found " + node); + } + } + + private IDictionary _DeserializeToGenericDictionary ( + JSONNode node, + Type keyType, + Type valueType, + NodeOptions options, + ObjectTypes types = ObjectTypes.JSON, + Type[] customTypes = null) + { + IDictionary dictionary = (IDictionary)Activator + .CreateInstance (typeof(Dictionary<,>) + .MakeGenericType (keyType, valueType)); + _FeedDictionary (dictionary, node, keyType, valueType, options, types, customTypes); + return dictionary; + } + + private void _FeedDictionary ( + IDictionary dictionary, + JSONNode node, + Type keyType, + Type valueType, + NodeOptions options, + ObjectTypes types = ObjectTypes.JSON, + Type[] customTypes = null) + { + if (node.IsObject) { + JSONObject obj = node as JSONObject; + IEnumerator enumerator = obj.GetEnumerator (); + while (enumerator.MoveNext ()) { + var pair = (KeyValuePair)enumerator.Current; + // Use default field options to throw at any error. + object key = _Deserialize ( + new JSONString (pair.Key), + keyType, + NodeOptions.Default, + ObjectTypes.JSON, + null /* customTypes */); + + // Throws an error if needed. + object value = _Deserialize ( + pair.Value, + valueType, + options & ~NodeOptions.ReplaceDeserialized, + types, + customTypes); + dictionary.Add (key, value); + } + } else { + _HandleMismatch (options, "Expected a dictionary, found " + node); + } + } + + private object _DeserializeCustom (JSONNode node, Type type, NodeOptions options) + { + var conditionalAttributes = type.GetCustomAttributes (typeof(ConditionalInstantiationAttribute), false); + foreach (object attribute in conditionalAttributes) { + var condition = attribute as ConditionalInstantiationAttribute; + if (Equals (node [condition.key].Value, condition.value.ToString())) { + if (condition.removeKey) { + node.Remove (condition.key); + } + return Deserialize (node, condition.referenceType, options); + } + } + + var defaultAttribute = Util.GetAttribute (type); + if (defaultAttribute != null) { + return Deserialize (node, defaultAttribute.referenceType, options); + } + + object obj; + try { + obj = Activator.CreateInstance (type); + } catch (Exception) { + return _HandleUnknown (options, "Unknown type " + type + " cannot be instantiated."); + } + + DeserializeOn (obj, node, options); + return obj; + } + + private void _FeedCustom (object filledObject, JSONNode node, NodeOptions options) + { + if (filledObject is IDeserializable) { + (filledObject as IDeserializable).Deserialize (node, this); + return; + } + + var listener = filledObject as IDeserializationListener; + if (listener != null) { + listener.OnDeserializationWillBegin (this); + } + + if (node.IsObject) { + try { + Type type = filledObject.GetType (); + + MemberInfo extrasMember = null; + JSONExtrasAttribute extrasAttribute = null; + Dictionary extras = new Dictionary (); + + var members = _GetDeserializedClassMembers (type, out extrasMember, out extrasAttribute); + JSONObject obj = node as JSONObject; + IEnumerator enumerator = obj.GetEnumerator (); + + var extrasTypeAttribute = extrasMember == null + ? null : Util.GetAttribute (extrasMember); + ObjectTypes extrasTypes = extrasTypeAttribute == null + ? ObjectTypes.JSON : extrasTypeAttribute.types; + Type[] extrasCustomTypes = extrasTypeAttribute == null + ? null : extrasTypeAttribute.customTypes; + + while (enumerator.MoveNext ()) { + var pair = (KeyValuePair)enumerator.Current; + if (members.ContainsKey (pair.Key)) { + _DeserializeClassMember (filledObject, members [pair.Key], pair.Value); + } else { + if (extrasMember != null) { + extras.Add (pair.Key, _DeserializeToObject ( + pair.Value, + extrasAttribute.options, + extrasTypes, + extrasCustomTypes)); + continue; + } + + var objectAttribute = Util.GetAttribute (type); + if (objectAttribute == null || objectAttribute.options.ShouldThrowAtUnknownKey ()) { + throw new DeserializationException ("The key " + pair.Key + " does not exist " + + "in class " + type); + } + } + } + + if (extrasMember != null) { + if (extras.Count != 0 || extrasAttribute.options.ShouldAssignNull ()) { + Util.SetMemberValue (extrasMember, filledObject, extras); + } + } + + if (listener != null) { + listener.OnDeserializationSucceeded (this); + } + } catch (Exception exception) { + if (listener != null) { + listener.OnDeserializationFailed (this); + } + throw exception; + } + } else { + if (listener != null) { + listener.OnDeserializationFailed (this); + } + _HandleMismatch (options, "Expected a JSON object, found " + node); + } + } + + private void _DeserializeClassMember ( + object filledObject, + List memberInfos, + JSONNode node) + { + for (int i = 0; i < memberInfos.Count; i++) { + var memberInfo = memberInfos [i]; + var fieldAttribute = Util.GetAttribute (memberInfo); + var options = fieldAttribute != null ? fieldAttribute.options : NodeOptions.Default; + + try { + Type type = Util.GetMemberType (memberInfo); + if (node.IsObject + && !type.IsValueType + && !typeof(IDictionary).IsAssignableFrom (type) + && !options.ShouldReplaceWithDeserialized ()) { + var value = Util.GetMemberValue (memberInfo, filledObject); + if (value != null) { + DeserializeOn (value, node, options); + return; + } + } + + object deserialized = _Deserialize (node, type, options, memberInfo); + if (deserialized != null || options.ShouldAssignNull ()) { + Util.SetMemberValue (memberInfo, filledObject, deserialized); + return; + } + } catch (Exception ex) { + if (i == memberInfos.Count - 1) { + throw ex; + } + } + } + } + + private Dictionary> _GetDeserializedClassMembers ( + Type classType, + out MemberInfo extrasMember, + out JSONExtrasAttribute extrasAttribute) + { + JSONObjectAttribute classAttribute = Util.GetAttribute (classType); + Dictionary> members = new Dictionary> (); + + var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + if (classAttribute != null && !classAttribute.options.ShouldIgnoreStatic ()) { + flags |= BindingFlags.Static; + } + + extrasMember = null; + extrasAttribute = null; + + foreach (var fieldInfo in classType.GetFields(flags)) { + if (extrasMember == null) { + if (Util.IsJSONExtrasMember (fieldInfo, out extrasAttribute)) { + extrasMember = fieldInfo; + continue; + } + } + + var fieldAttribute = Util.GetAttribute (fieldInfo); + if (fieldAttribute != null && !fieldAttribute.options.IsDeserialized ()) { + continue; + } else if (!fieldInfo.IsLiteral && (fieldInfo.IsPublic || fieldAttribute != null)) { + string key = (fieldAttribute != null && fieldAttribute.key != null) + ? fieldAttribute.key : fieldInfo.Name; + if (!members.ContainsKey (key)) { + members [key] = new List (); + } + members [key].Add (fieldInfo); + } + } + + if (classAttribute == null || !classAttribute.options.ShouldIgnoreProperties ()) { + foreach (var propertyInfo in classType.GetProperties(flags)) { + if (extrasMember == null) { + if (Util.IsJSONExtrasMember (propertyInfo, out extrasAttribute)) { + extrasMember = propertyInfo; + continue; + } + } + + var fieldAttribute = Util.GetAttribute (propertyInfo); + if (fieldAttribute != null && !fieldAttribute.options.IsDeserialized ()) { + continue; + } else if (propertyInfo.GetIndexParameters ().Length == 0 && propertyInfo.CanWrite && + (fieldAttribute != null || propertyInfo.GetSetMethod (false) != null)) { + string key = (fieldAttribute != null && fieldAttribute.key != null) + ? fieldAttribute.key : propertyInfo.Name; + if (!members.ContainsKey (key)) { + members [key] = new List (); + } + members [key].Add (propertyInfo); + } + } + } + + return members; + } + + private object _HandleMismatch (NodeOptions options, string message) + { + if (!options.ShouldIgnoreTypeMismatch ()) { + throw new DeserializationException (message); + } else { + return null; + } + } + + private object _HandleUnknown (NodeOptions options, string message) + { + if (!options.ShouldIgnoreUnknownType ()) { + throw new DeserializationException (message); + } else { + return null; + } + } + } +} diff --git a/Assets/Plugins/UnityJSON/Deserializer.cs.meta b/Assets/Plugins/UnityJSON/Deserializer.cs.meta new file mode 100644 index 0000000..be5d1a7 --- /dev/null +++ b/Assets/Plugins/UnityJSON/Deserializer.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b8fef390dd220476eb25f623b29ad0f0 +timeCreated: 1505244350 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/UnityJSON/Enums.cs b/Assets/Plugins/UnityJSON/Enums.cs new file mode 100644 index 0000000..650bfef --- /dev/null +++ b/Assets/Plugins/UnityJSON/Enums.cs @@ -0,0 +1,335 @@ +namespace UnityJSON +{ + /// + /// Serialization/deserialization options for a single + /// JSON node. These can be defined for the fields and properties + /// of a class with the use of the JSONNodeAttribute. If no + /// attribute is given, the default options are used. + /// + public enum NodeOptions + { + /// + /// Default options for the serialization and deserialization + /// of this node which disables all the other node options. + /// + Default = 0, + + /// + /// If true the field or the property is not serialized. + /// This can be used with public fields / properties to + /// ignore them during serialization. + /// + DontSerialize = 1, + + /// + /// If true the field or the property is not deserialized. + /// This can be used with public fields / properties to + /// ignore them during deserialization. + /// + DontDeserialize = 1 << 1, + + /// + /// Per default, if a field or a property has the value null + /// then it is ignored at serialization. When used, this option + /// forces the field to be serialized as either null or undefined + /// (see Serializer.useUndefinedForNull). + /// + SerializeNull = 1 << 2, + + /// + /// Per default if the JSON data does not match the C# type, the + /// deserializer throws an error. When used, this forces the deserializer + /// to ignore this error and simply return null from the deserialization. + /// + IgnoreTypeMismatch = 1 << 3, + + /// + /// If the C# type cannot be instantiated, then an error is thrown. + /// This can for instance happen for classes with custom constructors + /// that are not defined in the deserializer. When used, this option + /// forces the deserializer to ignore this error and simply return null + /// from the deserialization. + /// + IgnoreUnknownType = 1 << 4, + + /// + /// Ignore both the type mismatch and the unknown type errors for + /// deserialization. + /// + IgnoreDeserializationTypeErrors = IgnoreTypeMismatch | IgnoreUnknownType, + + /// + /// When deserializing a JSON string, the null or undefined values are + /// not assigned and simply ignored. When used, this option makes sure + /// that the null values are applied to this field or property. If this + /// has a primitive type, then the default value (0 for integer, false + /// for boolean etc) is assigned. + /// + DontAssignNull = 1 << 5, + + /// + /// By default, the custom classes (all except string, nullables and + /// enumerables) are not assigned anew but rather reused. For instance, + /// assume we have classes A and B, the class A has field classB of + /// type B, class B has a field intField of type int. When deserializing A, + /// the deserializer by default sets the value of intField directly instead + /// of creating a new instance of class B with the new intField value. When + /// ReplaceDeserialized option is used on the field classB, the deserializer + /// creates a new instance of class B and assigns it directly instead of + /// working with the current value. + /// + ReplaceDeserialized = 1 << 6 + } + + /// + /// General serialization/deserialization options assigned to + /// all instances of a class or a struct. You can specify these options + /// by using a JSONObjectAttribute. + /// + public enum ObjectOptions + { + /// + /// Default options for the serialization and deserialization + /// of this class or struct which disables all the other object options. + /// + Default = 0, + + /// + /// Ignores the properties when serializing or deserializing. This + /// can also be used to simply increase the performance if no properties + /// are used for the serialization / deserialization. + /// + IgnoreProperties = 1, + + /// + /// Per default, the static fields and properties are ignored for + /// the serialization and deserialization. When used, all public + /// and non-public static fields and properties are considered for + /// serialization and deserialization just like the non-static ones. + /// + IncludeStatic = 1 << 1, + + /// + /// When deserializing the JSON, there can be unknown nodes that + /// do not match the class or struct definition. If there also isn't + /// any field / property with JSONExtrasAttribute to collect the unknown + /// keys, then an exception is thrown. This option prevents the + /// deserializer from throwing that exception. + /// + IgnoreUnknownKey = 1 << 2 + } + + /// + /// The types that are supported for deserialization for a general + /// System.Object type. + /// + public enum ObjectTypes + { + /// + /// Default value simply deserializes the JSON objects into + /// their C# counterparts as in strings, booleans, doubles + /// and arrays and dictionaries thereof. + /// + JSON = String | Bool | Number | Array | Dictionary, + + /// + /// Allows all types including custom types. + /// + All = JSON | Custom, + + /// + /// Allows strings. + /// + String = 1, + + /// + /// Allows booleans. + /// + Bool = 1 << 1, + + /// + /// Allows numbers in form of doubles. + /// + Number = 1 << 2, + + /// + /// Allows arrays of the supported + /// object types. + /// + Array = 1 << 3, + + /// + /// Allows dictionaries with string keys + /// and values of supported object types. + /// + Dictionary = 1 << 4, + + /// + /// Allows custom types. These must be defined + /// in a separate type array. + /// + Custom = 1 << 5 + } + + /// + /// Formatting to be applied to enum members before + /// serializing or deserializing them. + /// + public enum JSONEnumMemberFormating + { + /// + /// No formatting is applied. + /// + None, + + /// + /// The member name is lowecased. + /// + Lowercased, + + /// + /// The member name is uppercased. + /// + Uppercased, + + /// + /// The member name is capitalized with the + /// only first letter capital. + /// + Capitalized + } + + /// + /// Provides helper methods to query options. + /// + public static class OptionsExtensions + { + /// + /// Returns true if NodeOptions.DontSerialize is not set. + /// + public static bool IsSerialized (this NodeOptions options) + { + return (options & NodeOptions.DontSerialize) == 0; + } + + /// + /// Returns true if NodeOptions.DontDeserialize is not set. + /// + public static bool IsDeserialized (this NodeOptions options) + { + return (options & NodeOptions.DontDeserialize) == 0; + } + + /// + /// Returns true if NodeOptions.ShouldSerializeNull is set. + /// + public static bool ShouldSerializeNull (this NodeOptions options) + { + return (options & NodeOptions.SerializeNull) != 0; + } + + /// + /// Returns true if NodeOptions.IgnoreTypeMismatch is set. + /// + public static bool ShouldIgnoreTypeMismatch (this NodeOptions options) + { + return (options & NodeOptions.IgnoreTypeMismatch) != 0; + } + + /// + /// Returns true if NodeOptions.IgnoreUnknownType is set. + /// + public static bool ShouldIgnoreUnknownType (this NodeOptions options) + { + return (options & NodeOptions.IgnoreUnknownType) != 0; + } + + /// + /// Returns true if NodeOptions.DontAssignNull is not set. + /// + public static bool ShouldAssignNull (this NodeOptions options) + { + return (options & NodeOptions.DontAssignNull) == 0; + } + + /// + /// Returns true if NodeOptions.ReplaceDeserialized is set. + /// + public static bool ShouldReplaceWithDeserialized (this NodeOptions options) + { + return (options & NodeOptions.ReplaceDeserialized) != 0; + } + + /// + /// Returns true if ObjectOptions.IgnoreProperties is set. + /// + public static bool ShouldIgnoreProperties (this ObjectOptions options) + { + return (options & ObjectOptions.IgnoreProperties) != 0; + } + + /// + /// Returns true if ObjectOptions.IncludeStatic is not set. + /// + public static bool ShouldIgnoreStatic (this ObjectOptions options) + { + return (options & ObjectOptions.IncludeStatic) == 0; + } + + /// + /// Returns true if ObjectOptions.IgnoreUnknownKey is not set. + /// + public static bool ShouldThrowAtUnknownKey (this ObjectOptions options) + { + return (options & ObjectOptions.IgnoreUnknownKey) == 0; + } + + /// + /// Returns true if ObjectTypes.String is set. + /// + public static bool SupportsString (this ObjectTypes types) + { + return (types & ObjectTypes.String) != 0; + } + + /// + /// Returns true if ObjectTypes.Bool is set. + /// + public static bool SupportsBool (this ObjectTypes types) + { + return (types & ObjectTypes.Bool) != 0; + } + + /// + /// Returns true if ObjectTypes.Number is set. + /// + public static bool SupportsNumber (this ObjectTypes types) + { + return (types & ObjectTypes.Number) != 0; + } + + /// + /// Returns true if ObjectTypes.Array is set. + /// + public static bool SupportsArray (this ObjectTypes types) + { + return (types & ObjectTypes.Array) != 0; + } + + /// + /// Returns true if ObjectTypes.Dictionary is set. + /// + public static bool SupportsDictionary (this ObjectTypes types) + { + return (types & ObjectTypes.Dictionary) != 0; + } + + /// + /// Returns true if ObjectTypes.Custom is set. + /// + public static bool SupportsCustom (this ObjectTypes types) + { + return (types & ObjectTypes.Custom) != 0; + } + } +} diff --git a/Assets/Plugins/UnityJSON/Enums.cs.meta b/Assets/Plugins/UnityJSON/Enums.cs.meta new file mode 100644 index 0000000..f85e8d9 --- /dev/null +++ b/Assets/Plugins/UnityJSON/Enums.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: aa9f64f7249024d36945056a36c0b401 +timeCreated: 1505381989 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/UnityJSON/Interfaces.cs b/Assets/Plugins/UnityJSON/Interfaces.cs new file mode 100644 index 0000000..9c7c9e9 --- /dev/null +++ b/Assets/Plugins/UnityJSON/Interfaces.cs @@ -0,0 +1,95 @@ +using SimpleJSON; +using System; +using System.Collections.Generic; + +namespace UnityJSON +{ + /// + /// Provides custom serialization for an object. If a + /// class or a struct inherits this interface, the serializer + /// simply calls this method to perform serialization. Please + /// notice that instances of ISerializable will not get + /// serialization lifecycle events even if they implement + /// ISerializationListener interface. + /// + public interface ISerializable + { + /// + /// Serializes the object into JSON string. You + /// can use the helper methods in the serializer. + /// + string Serialize (Serializer serializer); + } + + /// + /// Provides custom deserialization for an object after it + /// is instantiated. You may still need to create your own + /// deserializer if your class / struct does not have a + /// constructor without parameters. + /// + /// If a class/struct inherits this interface, the deserializer + /// completely leaves the deserialization process to the + /// interface after the possible instantiation of the object. + /// Please notice that instances of IDeserializable will not get + /// deserialization lifecycle events even if they implement + /// IDeserializationListener interface. + /// + public interface IDeserializable + { + /// + /// Feeds the JSON node into the object. You can use the + /// helper methods from the deserializer. + /// + void Deserialize(JSONNode node, Deserializer deserializer); + } + + /// + /// Listens for the serialization process on the object. + /// + public interface ISerializationListener + { + /// + /// Called just before the serialization of the object begins. + /// This call will always be followed by either OnSerializationSucceeded + /// or OnSerializationFailed but not both. + /// + void OnSerializationWillBegin(Serializer serializer); + + /// + /// Called immediately after a successful completion of this + /// object's serialization. + /// + void OnSerializationSucceeded(Serializer serializer); + + /// + /// Called when the serialization of the object fails for any + /// reason. This will be called just before throwing the exception. + /// + void OnSerializationFailed(Serializer serializer); + } + + /// + /// Listens for the deserialization process on the object. + /// + public interface IDeserializationListener + { + /// + /// Called just before the deserialization of the object begins. + /// This call will always be followed by either OnDeserializationSucceeded + /// or OnDeserializationFailed but not both. + /// + void OnDeserializationWillBegin(Deserializer deserializer); + + /// + /// Called immediately after a successful completion of this + /// object's deserialization. + /// + void OnDeserializationSucceeded(Deserializer deserializer); + + /// + /// Called when the deserialization of the object fails for any + /// reason. This will be called just before throwing the exception. + /// + void OnDeserializationFailed(Deserializer deserializer); + } +} diff --git a/Assets/Plugins/UnityJSON/Interfaces.cs.meta b/Assets/Plugins/UnityJSON/Interfaces.cs.meta new file mode 100644 index 0000000..02b5c56 --- /dev/null +++ b/Assets/Plugins/UnityJSON/Interfaces.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: de1b7a86fce434b98ba19932aee15cb3 +timeCreated: 1505462421 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/UnityJSON/JSON.cs b/Assets/Plugins/UnityJSON/JSON.cs new file mode 100644 index 0000000..8605035 --- /dev/null +++ b/Assets/Plugins/UnityJSON/JSON.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections; + +namespace UnityJSON +{ + /// + /// Provides high-level public API for the serialization and deserialization. + /// + public static class JSON + { + /// + /// Serializes the given object into JSON string. Throws an + /// error if the object is null. + /// + /// Object to be serialized. + /// Serialization options (optional). + /// Custom serializer. If not given, + /// the default serializer is used. + public static string Serialize ( + object obj, + NodeOptions options = NodeOptions.Default, + Serializer serializer = null) + { + if (obj == null) { + throw new ArgumentNullException ("obj"); + } + if (serializer == null) { + serializer = Serializer.Default; + } + return serializer.Serialize (obj, options); + } + + /// + /// Serializes the object into JSON string. + /// + /// Object to be serialized. + /// Serialization options (optional). + /// Custom serializer. If not given, + /// the default serializer is used. + public static string ToJSONString ( + this object obj, + NodeOptions options = NodeOptions.Default, + Serializer serializer = null) + { + return Serialize (obj, options, serializer); + } + + /// + /// Deserializes an object of the generic type from + /// the given JSON string. Throws an exception if the string + /// is null or not a valid JSON string. + /// + /// The JSON string to deserialize from. + /// Deserialization options (optional). + /// Custom deserializer. If not given, + /// the default deserializer is used. + /// The type of object to deserialize. + public static T Deserialize ( + string jsonString, + NodeOptions options = NodeOptions.Default, + Deserializer deserializer = null) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + if (deserializer == null) { + deserializer = Deserializer.Default; + } + + SimpleJSON.JSONNode node = SimpleJSON.JSON.Parse (jsonString); + if (node == null) { + throw new ArgumentException ("Argument is not a valid JSON string: " + jsonString); + } + return (T)deserializer.Deserialize (node, typeof(T), options); + } + + /// + /// Deserializes a JSON string on a previously existing object. + /// Throws an exception if the string is null or not a valid JSON string. + /// + /// Object to deserialize on. + /// The JSON string to deserialize from. + /// Deserialization options (optional). + /// Custom deserializer. If not given, + /// the default deserializer is used. + public static void DeserializeOn ( + object obj, + string jsonString, + NodeOptions options = NodeOptions.Default, + Deserializer deserializer = null) + { + if (obj == null) { + throw new ArgumentNullException ("obj"); + } + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + if (deserializer == null) { + deserializer = Deserializer.Default; + } + + SimpleJSON.JSONNode node = SimpleJSON.JSON.Parse (jsonString); + if (node == null) { + throw new ArgumentException ("Argument is not a valid JSON string: " + jsonString); + } + deserializer.DeserializeOn (obj, node, options); + } + + /// + /// Deserializes a JSON string on the previously existing object. + /// Throws an exception if the string is null or not a valid JSON string. + /// + /// Object to deserialize on. + /// The JSON string to deserialize from. + /// Deserialization options (optional). + /// Custom deserializer. If not given, + /// the default deserializer is used. + public static void FeedJSON ( + this object obj, + string jsonString, + NodeOptions options = NodeOptions.Default, + Deserializer deserializer = null) + { + DeserializeOn (obj, jsonString, options, deserializer); + } + } +} diff --git a/Assets/Plugins/UnityJSON/JSON.cs.meta b/Assets/Plugins/UnityJSON/JSON.cs.meta new file mode 100644 index 0000000..a87739e --- /dev/null +++ b/Assets/Plugins/UnityJSON/JSON.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 79ef6b78ebc5d4f0fae7a1c5aa95cb0a +timeCreated: 1505493113 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/UnityJSON/Serializer.cs b/Assets/Plugins/UnityJSON/Serializer.cs new file mode 100644 index 0000000..5a92777 --- /dev/null +++ b/Assets/Plugins/UnityJSON/Serializer.cs @@ -0,0 +1,487 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using UnityEngine; + +namespace UnityJSON +{ + /// + /// Serializes an object into JSON string. Subclasses should override + /// the #TrySerialize method. + /// + public class Serializer + { + private const string _kUndefined = "undefined"; + private const string _kNull = "null"; + private const string _kTrue = "true"; + private const string _kFalse = "false"; + + private static Serializer _default = new Serializer (); + private static Serializer _simple = _default; + + /// + /// The default serializer to be used when no serializer is given. + /// You can set this to your own default serializer. Uses the + /// #Simple serializer by default. + /// + public static Serializer Default { + get { return _default; } + set { + if (value == null) { + throw new ArgumentNullException ("default serializer"); + } + _simple = value; + } + } + + /// + /// The initial serializer that is provided by the framework. + /// + public static Serializer Simple { + get { return _simple; } + } + + /// + /// When set to true, the keyword undefined is used instead of null + /// when serializing. Defaults to false. + /// + public bool useUndefinedForNull = false; + + private Serializer() + { + } + + /// + /// Tries to perform application-specific serialization. This is called + /// first when the #Serialize method is called, and performs internal + /// serialization if it returns false. This method is called before + /// ISerializable.Serialize. + /// + /// Object to serialize. Object is guaranteed to be non-null. + /// Serialization options. + /// The serialized JSON string. + protected virtual bool TrySerialize ( + object obj, + NodeOptions options, + out string serialized) + { + serialized = null; + return false; + } + + /// + /// Serializes any object into JSON string according to the given + /// options. This will first run through the #TrySerialize method and + /// then ISerializable.Serialize method if the object implements it. + /// If these are not implemented, then the framework serialization is + /// performed. + /// + public string Serialize (object obj, NodeOptions options = NodeOptions.Default) + { + if (obj == null) { + return SerializeNull (options); + } + + string result = null; + if (TrySerialize (obj, options, out result)) { + return result; + } + + ISerializable serializable = obj as ISerializable; + if (serializable != null) { + return serializable.Serialize (this); + } + + if (obj != null) { + Type type = obj.GetType (); + if (type.IsValueType) { + if (type.IsEnum) { + result = _SerializeEnum ((Enum) obj); + } else if (type.IsPrimitive) { + if (obj is bool) { + result = SerializeBool ((bool)obj); + } else { + result = obj.ToString (); + } + } else { + if (obj is DictionaryEntry) { + result = _SerializeDictionaryEntry ((DictionaryEntry)obj, options); + } else if (obj is Vector2) { + result = SerializeVector2 ((Vector2)obj); + } else if (obj is Vector3) { + result = SerializeVector3 ((Vector3)obj); + } else if (obj is Vector4) { + result = SerializeVector4 ((Vector4)obj); + } else if (obj is Quaternion) { + result = SerializeQuaternion ((Quaternion)obj); + } else if (obj is Color) { + result = SerializeColor ((Color)obj); + } else if (obj is Rect) { + result = SerializeRect ((Rect)obj); + } else if (obj is Bounds) { + result = SerializeBounds ((Bounds)obj); + } else { + result = _SerializeCustom (obj, options); + } + } + } else { + if (obj is string) { + result = _SerializeString (obj as string); + } else if (Nullable.GetUnderlyingType (type) != null) { + result = _SerializeNullable (obj, options); + } else if (obj is IEnumerable) { + string enumerableJSON = _SerializeEnumarable (obj as IEnumerable, options); + if (obj is IDictionary) { + result = "{" + enumerableJSON + "}"; + } else { + result = "[" + enumerableJSON + "]"; + } + } else { + result = _SerializeCustom (obj, options); + } + } + } + + if (result == null) { + return SerializeNull (options); + } else { + return result; + } + } + + /// + /// Serializes null if NodeOptions.SerializeNull is used or + /// returns null otherwise. Null can be serialized as + /// "null" or "undefined" according to the value of #useUndefinedForNull. + /// + public string SerializeNull (NodeOptions options) + { + return options.ShouldSerializeNull () ? + (useUndefinedForNull ? _kUndefined : _kNull) : null; + } + + /// + /// Serializes a string into JSON string with the optional + /// node options. + /// + public string SerializeString ( + string stringValue, + NodeOptions options = NodeOptions.Default) + { + if (stringValue == null) { + return SerializeNull (options); + } + return _SerializeString (stringValue); + } + + /// + /// Serializes an enum into JSON string. Throws ArgumentNullException + /// if the value is null. + /// + public string SerializeEnum (Enum enumValue) + { + if (enumValue == null) { + throw new ArgumentNullException ("enumValue"); + } + return _SerializeEnum (enumValue); + } + + /// + /// Serializes a System.Nullable object into JSON string. + /// Throws an ArgumentException if the object is not of a + /// nullable type. + /// + public string SerializeNullable ( + object nullable, + NodeOptions options = NodeOptions.Default) + { + if (nullable == null) { + return SerializeNull (options); + } + if (Nullable.GetUnderlyingType (nullable.GetType ()) == null) { + throw new ArgumentException ("Argument is not a nullable object."); + } + return _SerializeNullable (nullable, options); + } + + /// + /// Serializes a boolean value. + /// + public string SerializeBool (bool boolValue) + { + return boolValue ? _kTrue : _kFalse; + } + + /// + /// Serializes Unity Vector2 into JSON string. + /// + public string SerializeVector2 (Vector2 vector) + { + return "{\"x\":" + vector.x + ",\"y\":" + vector.y + "}"; + } + + /// + /// Serializes Unity Vector3 into JSON string. + /// + public string SerializeVector3 (Vector3 vector) + { + return "{\"x\":" + vector.x + ",\"y\":" + vector.y + ",\"z\":" + vector.z + "}"; + } + + /// + /// Serializes Unity Vector4 into JSON string. + /// + public string SerializeVector4 (Vector4 vector) + { + return "{\"x\":" + vector.x + ",\"y\":" + vector.y + + ",\"z\":" + vector.z + ",\"w\":" + vector.w + "}"; + } + + /// + /// Serializes Unity Quaternion into JSON string. + /// + public string SerializeQuaternion (Quaternion quaternion) + { + return "{\"x\":" + quaternion.x + ",\"y\":" + quaternion.y + + ",\"z\":" + quaternion.z + ",\"w\":" + quaternion.w + "}"; + } + + /// + /// Serializes Unity Color into JSON string. + /// + public string SerializeColor (Color color) + { + return "{\"r\":" + color.r + ",\"g\":" + color.g + + ",\"b\":" + color.b + ",\"a\":" + color.a + "}"; + } + + /// + /// Serializes Unity Rect into JSON string. + /// + private string SerializeRect (Rect rect) + { + return "{\"x\":" + rect.x + ",\"y\":" + rect.y + + ",\"width\":" + rect.width + ",\"height\":" + rect.height + "}"; + } + + /// + /// Serializes Unity Bounds into JSON string. + /// + private string SerializeBounds (Bounds bounds) + { + return "{\"center\":" + SerializeVector3 (bounds.center) + + ",\"extents\":" + SerializeVector3 (bounds.extents) + "}"; + } + + private string _SerializeString (string stringValue) + { + return "\"" + stringValue.Replace ("\"", "\\\"") + "\""; + } + + private string _SerializeEnum (Enum obj) + { + Type type = obj.GetType (); + JSONEnumAttribute enumAttribute = Util.GetAttribute (type); + if (enumAttribute != null) { + if (enumAttribute.useIntegers) { + return (Convert.ToInt32(obj)).ToString (); + } else { + string formatted = _FormatEnumMember (obj.ToString (), enumAttribute.format); + if (enumAttribute.prefix != null) { + formatted = enumAttribute.prefix + formatted; + } + if (enumAttribute.suffix != null) { + formatted += enumAttribute.suffix; + } + return _SerializeString (formatted); + } + } else { + return _SerializeString (obj.ToString ()); + } + } + + private string _SerializeNullable (object nullable, NodeOptions options) + { + Type type = nullable.GetType (); + if (!(bool)(type.GetProperty ("HasValue").GetValue (nullable, null))) { + return SerializeNull (options); + } + + return Serialize (type.GetProperty ("Value").GetValue (nullable, null), options); + } + + private string _SerializeEnumarable (IEnumerable enumerable, NodeOptions options) + { + if (enumerable is IList) { + // Always serialize nulls for arrays. + options |= NodeOptions.SerializeNull; + } + string joined = _Join (enumerable, obj => Serialize (obj, options)); + if (joined == null) { + return SerializeNull (options); + } else { + return joined; + } + } + + private string _SerializeDictionaryEntry (DictionaryEntry entry, NodeOptions options) + { + // Don't serialize nulls for keys. If a key is null, its pair + // shouldn't be serialized as undefined:value. + NodeOptions keyOptions = options & ~NodeOptions.SerializeNull; + string serializedKey = Serialize (entry.Key, keyOptions); + if (serializedKey == null) { + return SerializeNull (options); + } + + string valueKey = Serialize (entry.Value, options); + if (valueKey == null) { + return null; + } else { + string jsonKey = entry.Key is string ? serializedKey : _SerializeString (serializedKey); + return jsonKey + ":" + valueKey; + } + } + + private string _SerializeCustom (object obj, NodeOptions options) + { + ISerializationListener listener = obj as ISerializationListener; + if (listener != null) { + listener.OnSerializationWillBegin (this); + } + + try { + IEnumerable enumerable = new string[] { }; + MemberInfo extrasMember = null; + JSONExtrasAttribute extrasAttribute = null; + + // Find member info and attribute for extras while going over the + // fields and properties. + Func isNotExtras = m => { + if (extrasMember == null && Util.IsJSONExtrasMember (m, out extrasAttribute)) { + extrasMember = m; + return false; + } else { + return true; + } + }; + + Type type = obj.GetType (); + JSONObjectAttribute classAttribute = Util.GetAttribute (type); + var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + if (classAttribute != null && !classAttribute.options.ShouldIgnoreStatic ()) { + flags |= BindingFlags.Static; + } + + enumerable = enumerable.Concat ( + from f in type.GetFields (flags) + where isNotExtras (f) && _IsValidFieldInfo (f) + select _SerializeCustomField (obj, f)); + + if (classAttribute == null || !classAttribute.options.ShouldIgnoreProperties ()) { + enumerable = enumerable.Concat ( + from p in type.GetProperties (flags) + where isNotExtras (p) && _IsValidPropertyInfo (p) + select _SerializeCustomProperty (obj, p)); + } + + // Serialize all properties and fields. + var result = _Join (enumerable, o => o as string); + + // Serialize the extras if there are any. + if (extrasMember != null) { + var extras = Util.GetMemberValue (extrasMember, obj) as IEnumerable; + if (extras != null) { + result += (result == "" ? "" : ",") + + _SerializeEnumarable (extras, extrasAttribute.options); + } + } + + if (listener != null) { + listener.OnSerializationSucceeded (this); + } + return "{" + result + "}";; + } catch (Exception exception) { + if (listener != null) { + listener.OnSerializationFailed (this); + } + throw exception; + } + } + + private bool _IsValidFieldInfo (FieldInfo fieldInfo) + { + return fieldInfo.IsPublic || Attribute.IsDefined (fieldInfo, typeof(JSONNodeAttribute), true); + } + + private bool _IsValidPropertyInfo (PropertyInfo propertyInfo) + { + return propertyInfo.GetIndexParameters ().Length == 0 && propertyInfo.CanRead && + (propertyInfo.GetGetMethod (false) != null || + Attribute.IsDefined (propertyInfo, typeof(JSONNodeAttribute), true)); + } + + private string _FormatEnumMember (string member, JSONEnumMemberFormating format) + { + switch (format) { + case JSONEnumMemberFormating.Lowercased: + return member.ToLower (); + case JSONEnumMemberFormating.Uppercased: + return member.ToUpper (); + case JSONEnumMemberFormating.Capitalized: + return Char.ToUpper (member [0]) + member.Substring (1).ToLower (); + default: + return member; + } + } + + private string _Join (IEnumerable enumerable, Func serializer) + { + string result = ""; + bool firstAdded = false; + + IEnumerator enumerator = enumerable is IDictionary ? + (enumerable as IDictionary).GetEnumerator () : + enumerable.GetEnumerator (); + + while (enumerator.MoveNext ()) { + string serialized = serializer (enumerator.Current); + if (serialized != null) { + string prefix = firstAdded ? "," : ""; + firstAdded = true; + result += prefix + serialized; + } + } + return result; + } + + private string _SerializeCustomField (object obj, FieldInfo fieldInfo) + { + return _SerializeCustomMember (fieldInfo, fieldInfo.GetValue (obj)); + } + + private string _SerializeCustomProperty (object obj, PropertyInfo propertyInfo) + { + return _SerializeCustomMember (propertyInfo, propertyInfo.GetValue (obj, null)); + } + + private string _SerializeCustomMember (MemberInfo keyMemberInfo, object value) + { + JSONNodeAttribute attribute = Util.GetAttribute (keyMemberInfo); + NodeOptions options = attribute == null ? NodeOptions.Default : attribute.options; + if (!options.IsSerialized ()) { + return null; + } + + string valueString = Serialize (value, options); + if (valueString != null || options.ShouldSerializeNull ()) { + string key = (attribute != null && attribute.key != null) ? attribute.key : keyMemberInfo.Name; + return _SerializeString (key) + ":" + (valueString == null ? _kUndefined : valueString); + } else { + return null; + } + } + } +} diff --git a/Assets/Plugins/UnityJSON/Serializer.cs.meta b/Assets/Plugins/UnityJSON/Serializer.cs.meta new file mode 100644 index 0000000..75a902b --- /dev/null +++ b/Assets/Plugins/UnityJSON/Serializer.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 50efb33f1602f4d9197ef073f1b025c5 +timeCreated: 1505244340 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/UnityJSON/SimpleJSON.cs b/Assets/Plugins/UnityJSON/SimpleJSON.cs new file mode 100644 index 0000000..38872a1 --- /dev/null +++ b/Assets/Plugins/UnityJSON/SimpleJSON.cs @@ -0,0 +1,1432 @@ +//#define USE_SharpZipLib +#if !UNITY_WEBPLAYER +#define USE_FileIO +#endif +/* * * * * + * A simple JSON Parser / builder + * ------------------------------ + * + * It mainly has been written as a simple JSON parser. It can build a JSON string + * from the node-tree, or generate a node tree from any valid JSON string. + * + * If you want to use compression when saving to file / stream / B64 you have to include + * SharpZipLib ( http://www.icsharpcode.net/opensource/sharpziplib/ ) in your project and + * define "USE_SharpZipLib" at the top of the file + * + * Written by Bunny83 + * 2012-06-09 + * + * + * Features / attributes: + * - provides strongly typed node classes and lists / dictionaries + * - provides easy access to class members / array items / data values + * - the parser now properly identifies types. So generating JSON with this framework should work. + * - only double quotes (") are used for quoting strings. + * - provides "casting" properties to easily convert to / from those types: + * int / float / double / bool + * - provides a common interface for each node so no explicit casting is required. + * - the parser tries to avoid errors, but if malformed JSON is parsed the result is more or less undefined + * - It can serialize/deserialize a node tree into/from an experimental compact binary format. It might + * be handy if you want to store things in a file and don't want it to be easily modifiable + * + * + * 2012-12-17 Update: + * - Added internal JSONLazyCreator class which simplifies the construction of a JSON tree + * Now you can simple reference any item that doesn't exist yet and it will return a JSONLazyCreator + * The class determines the required type by it's further use, creates the type and removes itself. + * - Added binary serialization / deserialization. + * - Added support for BZip2 zipped binary format. Requires the SharpZipLib ( http://www.icsharpcode.net/opensource/sharpziplib/ ) + * The usage of the SharpZipLib library can be disabled by removing or commenting out the USE_SharpZipLib define at the top + * - The serializer uses different types when it comes to store the values. Since my data values + * are all of type string, the serializer will "try" which format fits best. The order is: int, float, double, bool, string. + * It's not the most efficient way but for a moderate amount of data it should work on all platforms. + * + * 2017-03-08 Update: + * - Optimised parsing by using a StringBuilder for token. This prevents performance issues when large + * string data fields are contained in the json data. + * - Finally refactored the badly named JSONClass into JSONObject. + * - Replaced the old JSONData class by distict typed classes ( JSONString, JSONNumber, JSONBool, JSONNull ) this + * allows to propertly convert the node tree back to json without type information loss. The actual value + * parsing now happens at parsing time and not when you actually access one of the casting properties. + * + * 2017-04-11 Update: + * - Fixed parsing bug where empty string values have been ignored. + * - Optimised "ToString" by using a StringBuilder internally. This should heavily improve performance for large files + * - Changed the overload of "ToString(string aIndent)" to "ToString(int aIndent)" + * + * The MIT License (MIT) + * + * Copyright (c) 2012-2017 Markus Göbel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * * * * */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SimpleJSON +{ + public enum JSONNodeType + { + Array = 1, + Object = 2, + String = 3, + Number = 4, + NullValue = 5, + Boolean = 6, + None = 7, + } + public enum JSONTextMode + { + Compact, + Indent + } + + public abstract partial class JSONNode + { + #region common interface + + public virtual JSONNode this[int aIndex] { get { return null; } set { } } + + public virtual JSONNode this[string aKey] { get { return null; } set { } } + + public virtual string Value { get { return ""; } set { } } + + public virtual int Count { get { return 0; } } + + public virtual bool IsNumber { get { return false; } } + public virtual bool IsString { get { return false; } } + public virtual bool IsBoolean { get { return false; } } + public virtual bool IsNull { get { return false; } } + public virtual bool IsArray { get { return false; } } + public virtual bool IsObject { get { return false; } } + + public virtual void Add(string aKey, JSONNode aItem) + { + } + public virtual void Add(JSONNode aItem) + { + Add("", aItem); + } + + public virtual JSONNode Remove(string aKey) + { + return null; + } + + public virtual JSONNode Remove(int aIndex) + { + return null; + } + + public virtual JSONNode Remove(JSONNode aNode) + { + return aNode; + } + + public virtual IEnumerable Children + { + get + { + yield break; + } + } + + public IEnumerable DeepChildren + { + get + { + foreach (var C in Children) + foreach (var D in C.DeepChildren) + yield return D; + } + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + WriteToStringBuilder(sb, 0, 0, JSONTextMode.Compact); + return sb.ToString(); + } + + public virtual string ToString(int aIndent) + { + StringBuilder sb = new StringBuilder(); + WriteToStringBuilder(sb, 0, aIndent, JSONTextMode.Indent); + return sb.ToString(); + } + internal abstract void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode); + + #endregion common interface + + #region typecasting properties + + public abstract JSONNodeType Tag { get; } + + public virtual double AsDouble + { + get + { + double v = 0.0; + if (double.TryParse(Value, out v)) + return v; + return 0.0; + } + set + { + Value = value.ToString(); + } + } + + public virtual int AsInt + { + get { return (int)AsDouble; } + set { AsDouble = value; } + } + + public virtual float AsFloat + { + get { return (float)AsDouble; } + set { AsDouble = value; } + } + + public virtual bool AsBool + { + get + { + bool v = false; + if (bool.TryParse(Value, out v)) + return v; + return !string.IsNullOrEmpty(Value); + } + set + { + Value = (value) ? "true" : "false"; + } + } + + public virtual JSONArray AsArray + { + get + { + return this as JSONArray; + } + } + + public virtual JSONObject AsObject + { + get + { + return this as JSONObject; + } + } + + + #endregion typecasting properties + + #region operators + + public static implicit operator JSONNode(string s) + { + return new JSONString(s); + } + public static implicit operator string(JSONNode d) + { + return (d == null) ? null : d.Value; + } + + public static implicit operator JSONNode(double n) + { + return new JSONNumber(n); + } + public static implicit operator double(JSONNode d) + { + return (d == null) ? 0 : d.AsDouble; + } + + public static implicit operator JSONNode(float n) + { + return new JSONNumber(n); + } + public static implicit operator float(JSONNode d) + { + return (d == null) ? 0 : d.AsFloat; + } + + public static implicit operator JSONNode(int n) + { + return new JSONNumber(n); + } + public static implicit operator int(JSONNode d) + { + return (d == null) ? 0 : d.AsInt; + } + + public static implicit operator JSONNode(bool b) + { + return new JSONBool(b); + } + public static implicit operator bool(JSONNode d) + { + return (d == null) ? false : d.AsBool; + } + + public static bool operator ==(JSONNode a, object b) + { + if (ReferenceEquals(a, b)) + return true; + bool aIsNull = a is JSONNull || ReferenceEquals(a, null) || a is JSONLazyCreator; + bool bIsNull = b is JSONNull || ReferenceEquals(b, null) || b is JSONLazyCreator; + if (aIsNull && bIsNull) + return true; + return a.Equals(b); + } + + public static bool operator !=(JSONNode a, object b) + { + return !(a == b); + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + #endregion operators + internal static StringBuilder m_EscapeBuilder = new StringBuilder(); + internal static string Escape(string aText) + { + m_EscapeBuilder.Length = 0; + if (m_EscapeBuilder.Capacity < aText.Length + aText.Length / 10) + m_EscapeBuilder.Capacity = aText.Length + aText.Length / 10; + foreach (char c in aText) + { + switch (c) + { + case '\\': + m_EscapeBuilder.Append("\\\\"); + break; + case '\"': + m_EscapeBuilder.Append("\\\""); + break; + case '\n': + m_EscapeBuilder.Append("\\n"); + break; + case '\r': + m_EscapeBuilder.Append("\\r"); + break; + case '\t': + m_EscapeBuilder.Append("\\t"); + break; + case '\b': + m_EscapeBuilder.Append("\\b"); + break; + case '\f': + m_EscapeBuilder.Append("\\f"); + break; + default: + m_EscapeBuilder.Append(c); + break; + } + } + string result = m_EscapeBuilder.ToString(); + m_EscapeBuilder.Length = 0; + return result; + } + + static void ParseElement(JSONNode ctx, string token, string tokenName, bool quoted) + { + if (quoted) + { + ctx.Add(tokenName, token); + return; + } + string tmp = token.ToLower(); + if (tmp == "false" || tmp == "true") + ctx.Add(tokenName, tmp == "true"); + else if (tmp == "null") + ctx.Add(tokenName, null); + else if (tmp == "undefined") + ctx.Add(tokenName, null); + else + { + double val; + if (double.TryParse(token, out val)) + ctx.Add(tokenName, val); + else + ctx.Add(tokenName, token); + } + } + + public static JSONNode Parse(string aJSON) + { + Stack stack = new Stack(); + JSONNode ctx = null; + int i = 0; + StringBuilder Token = new StringBuilder(); + string TokenName = ""; + bool QuoteMode = false; + bool TokenIsQuoted = false; + while (i < aJSON.Length) + { + switch (aJSON[i]) + { + case '{': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + stack.Push(new JSONObject()); + if (ctx != null) + { + ctx.Add(TokenName, stack.Peek()); + } + TokenName = ""; + Token.Length = 0; + ctx = stack.Peek(); + break; + + case '[': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + + stack.Push(new JSONArray()); + if (ctx != null) + { + ctx.Add(TokenName, stack.Peek()); + } + TokenName = ""; + Token.Length = 0; + ctx = stack.Peek(); + break; + + case '}': + case ']': + if (QuoteMode) + { + + Token.Append(aJSON[i]); + break; + } + if (stack.Count == 0) + throw new Exception("JSON Parse: Too many closing brackets"); + + stack.Pop(); + if (Token.Length > 0 || TokenIsQuoted) + { + ParseElement(ctx, Token.ToString(), TokenName, TokenIsQuoted); + TokenIsQuoted = false; + } + TokenName = ""; + Token.Length = 0; + if (stack.Count > 0) + ctx = stack.Peek(); + break; + + case ':': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + TokenName = Token.ToString(); + Token.Length = 0; + TokenIsQuoted = false; + break; + + case '"': + QuoteMode ^= true; + TokenIsQuoted |= QuoteMode; + break; + + case ',': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + if (Token.Length > 0 || TokenIsQuoted) + { + ParseElement(ctx, Token.ToString(), TokenName, TokenIsQuoted); + TokenIsQuoted = false; + } + TokenName = ""; + Token.Length = 0; + TokenIsQuoted = false; + break; + + case '\r': + case '\n': + break; + + case ' ': + case '\t': + if (QuoteMode) + Token.Append(aJSON[i]); + break; + + case '\\': + ++i; + if (QuoteMode) + { + char C = aJSON[i]; + switch (C) + { + case 't': + Token.Append('\t'); + break; + case 'r': + Token.Append('\r'); + break; + case 'n': + Token.Append('\n'); + break; + case 'b': + Token.Append('\b'); + break; + case 'f': + Token.Append('\f'); + break; + case 'u': + { + string s = aJSON.Substring(i + 1, 4); + Token.Append((char)int.Parse( + s, + System.Globalization.NumberStyles.AllowHexSpecifier)); + i += 4; + break; + } + default: + Token.Append(C); + break; + } + } + break; + + default: + Token.Append(aJSON[i]); + break; + } + ++i; + } + if (QuoteMode) + { + throw new Exception("JSON Parse: Quotation marks seems to be messed up."); + } + return ctx; + } + + public virtual void Serialize(System.IO.BinaryWriter aWriter) + { + } + + public void SaveToStream(System.IO.Stream aData) + { + var W = new System.IO.BinaryWriter(aData); + Serialize(W); + } + + #if USE_SharpZipLib + public void SaveToCompressedStream(System.IO.Stream aData) + { + using (var gzipOut = new ICSharpCode.SharpZipLib.BZip2.BZip2OutputStream(aData)) + { + gzipOut.IsStreamOwner = false; + SaveToStream(gzipOut); + gzipOut.Close(); + } + } + + public void SaveToCompressedFile(string aFileName) + { + + #if USE_FileIO + System.IO.Directory.CreateDirectory((new System.IO.FileInfo(aFileName)).Directory.FullName); + using(var F = System.IO.File.OpenWrite(aFileName)) + { + SaveToCompressedStream(F); + } + + #else + throw new Exception("Can't use File IO stuff in the webplayer"); + #endif + } + public string SaveToCompressedBase64() + { + using (var stream = new System.IO.MemoryStream()) + { + SaveToCompressedStream(stream); + stream.Position = 0; + return System.Convert.ToBase64String(stream.ToArray()); + } + } + + #else + public void SaveToCompressedStream(System.IO.Stream aData) + { + throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); + } + + public void SaveToCompressedFile(string aFileName) + { + throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); + } + + public string SaveToCompressedBase64() + { + throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); + } + #endif + + public void SaveToFile(string aFileName) + { + #if USE_FileIO + System.IO.Directory.CreateDirectory((new System.IO.FileInfo(aFileName)).Directory.FullName); + using (var F = System.IO.File.OpenWrite(aFileName)) + { + SaveToStream(F); + } + #else + throw new Exception ("Can't use File IO stuff in the webplayer"); + #endif + } + + public string SaveToBase64() + { + using (var stream = new System.IO.MemoryStream()) + { + SaveToStream(stream); + stream.Position = 0; + return System.Convert.ToBase64String(stream.ToArray()); + } + } + + public static JSONNode Deserialize(System.IO.BinaryReader aReader) + { + JSONNodeType type = (JSONNodeType)aReader.ReadByte(); + switch (type) + { + case JSONNodeType.Array: + { + int count = aReader.ReadInt32(); + JSONArray tmp = new JSONArray(); + for (int i = 0; i < count; i++) + tmp.Add(Deserialize(aReader)); + return tmp; + } + case JSONNodeType.Object: + { + int count = aReader.ReadInt32(); + JSONObject tmp = new JSONObject(); + for (int i = 0; i < count; i++) + { + string key = aReader.ReadString(); + var val = Deserialize(aReader); + tmp.Add(key, val); + } + return tmp; + } + case JSONNodeType.String: + { + return new JSONString(aReader.ReadString()); + } + case JSONNodeType.Number: + { + return new JSONNumber(aReader.ReadDouble()); + } + case JSONNodeType.Boolean: + { + return new JSONBool(aReader.ReadBoolean()); + } + case JSONNodeType.NullValue: + { + return new JSONNull(); + } + default: + { + throw new Exception("Error deserializing JSON. Unknown tag: " + type); + } + } + } + + #if USE_SharpZipLib + public static JSONNode LoadFromCompressedStream(System.IO.Stream aData) + { + var zin = new ICSharpCode.SharpZipLib.BZip2.BZip2InputStream(aData); + return LoadFromStream(zin); + } + public static JSONNode LoadFromCompressedFile(string aFileName) + { + #if USE_FileIO + using(var F = System.IO.File.OpenRead(aFileName)) + { + return LoadFromCompressedStream(F); + } + #else + throw new Exception("Can't use File IO stuff in the webplayer"); + #endif + } + public static JSONNode LoadFromCompressedBase64(string aBase64) + { + var tmp = System.Convert.FromBase64String(aBase64); + var stream = new System.IO.MemoryStream(tmp); + stream.Position = 0; + return LoadFromCompressedStream(stream); + } + #else + public static JSONNode LoadFromCompressedFile(string aFileName) + { + throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); + } + + public static JSONNode LoadFromCompressedStream(System.IO.Stream aData) + { + throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); + } + + public static JSONNode LoadFromCompressedBase64(string aBase64) + { + throw new Exception("Can't use compressed functions. You need include the SharpZipLib and uncomment the define at the top of SimpleJSON"); + } + #endif + + public static JSONNode LoadFromStream(System.IO.Stream aData) + { + using (var R = new System.IO.BinaryReader(aData)) + { + return Deserialize(R); + } + } + + public static JSONNode LoadFromFile(string aFileName) + { + #if USE_FileIO + using (var F = System.IO.File.OpenRead(aFileName)) + { + return LoadFromStream(F); + } + #else + throw new Exception ("Can't use File IO stuff in the webplayer"); + #endif + } + + public static JSONNode LoadFromBase64(string aBase64) + { + var tmp = System.Convert.FromBase64String(aBase64); + var stream = new System.IO.MemoryStream(tmp); + stream.Position = 0; + return LoadFromStream(stream); + } + } + // End of JSONNode + + public class JSONArray : JSONNode, IEnumerable + { + private List m_List = new List(); + public bool inline = false; + + public override JSONNodeType Tag { get { return JSONNodeType.Array; } } + public override bool IsArray { get { return true; } } + + public override JSONNode this[int aIndex] + { + get + { + if (aIndex < 0 || aIndex >= m_List.Count) + return new JSONLazyCreator(this); + return m_List[aIndex]; + } + set + { + if (value == null) + value = new JSONNull(); + if (aIndex < 0 || aIndex >= m_List.Count) + m_List.Add(value); + else + m_List[aIndex] = value; + } + } + + public override JSONNode this[string aKey] + { + get { return new JSONLazyCreator(this); } + set + { + if (value == null) + value = new JSONNull(); + m_List.Add(value); + } + } + + public override int Count + { + get { return m_List.Count; } + } + + public override void Add(string aKey, JSONNode aItem) + { + if (aItem == null) + aItem = new JSONNull(); + m_List.Add(aItem); + } + + public override JSONNode Remove(int aIndex) + { + if (aIndex < 0 || aIndex >= m_List.Count) + return null; + JSONNode tmp = m_List[aIndex]; + m_List.RemoveAt(aIndex); + return tmp; + } + + public override JSONNode Remove(JSONNode aNode) + { + m_List.Remove(aNode); + return aNode; + } + + public override IEnumerable Children + { + get + { + foreach (JSONNode N in m_List) + yield return N; + } + } + + public IEnumerator GetEnumerator() + { + foreach (JSONNode N in m_List) + yield return N; + } + + public override void Serialize(System.IO.BinaryWriter aWriter) + { + aWriter.Write((byte)JSONNodeType.Array); + aWriter.Write(m_List.Count); + for (int i = 0; i < m_List.Count; i++) + { + m_List[i].Serialize(aWriter); + } + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append('['); + int count = m_List.Count; + if (inline) + aMode = JSONTextMode.Compact; + for (int i = 0; i < count; i++) + { + if (i > 0) + aSB.Append(','); + if (aMode == JSONTextMode.Indent) + aSB.AppendLine(); + + if (aMode == JSONTextMode.Indent) + aSB.Append(' ', aIndent + aIndentInc); + m_List[i].WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode); + } + if (aMode == JSONTextMode.Indent) + aSB.AppendLine().Append(' ', aIndent); + aSB.Append(']'); + } + } + // End of JSONArray + + public class JSONObject : JSONNode, IEnumerable + { + private Dictionary m_Dict = new Dictionary(); + + public bool inline = false; + + public override JSONNodeType Tag { get { return JSONNodeType.Object; } } + public override bool IsObject { get { return true; } } + + + public override JSONNode this[string aKey] + { + get + { + if (m_Dict.ContainsKey(aKey)) + return m_Dict[aKey]; + else + return new JSONLazyCreator(this, aKey); + } + set + { + if (value == null) + value = new JSONNull(); + if (m_Dict.ContainsKey(aKey)) + m_Dict[aKey] = value; + else + m_Dict.Add(aKey, value); + } + } + + public override JSONNode this[int aIndex] + { + get + { + if (aIndex < 0 || aIndex >= m_Dict.Count) + return null; + return m_Dict.ElementAt(aIndex).Value; + } + set + { + if (value == null) + value = new JSONNull(); + if (aIndex < 0 || aIndex >= m_Dict.Count) + return; + string key = m_Dict.ElementAt(aIndex).Key; + m_Dict[key] = value; + } + } + + public override int Count + { + get { return m_Dict.Count; } + } + + public override void Add(string aKey, JSONNode aItem) + { + if (aItem == null) + aItem = new JSONNull(); + + if (!string.IsNullOrEmpty(aKey)) + { + if (m_Dict.ContainsKey(aKey)) + m_Dict[aKey] = aItem; + else + m_Dict.Add(aKey, aItem); + } + else + m_Dict.Add(Guid.NewGuid().ToString(), aItem); + } + + public override JSONNode Remove(string aKey) + { + if (!m_Dict.ContainsKey(aKey)) + return null; + JSONNode tmp = m_Dict[aKey]; + m_Dict.Remove(aKey); + return tmp; + } + + public override JSONNode Remove(int aIndex) + { + if (aIndex < 0 || aIndex >= m_Dict.Count) + return null; + var item = m_Dict.ElementAt(aIndex); + m_Dict.Remove(item.Key); + return item.Value; + } + + public override JSONNode Remove(JSONNode aNode) + { + try + { + var item = m_Dict.Where(k => k.Value == aNode).First(); + m_Dict.Remove(item.Key); + return aNode; + } + catch + { + return null; + } + } + + public override IEnumerable Children + { + get + { + foreach (KeyValuePair N in m_Dict) + yield return N.Value; + } + } + + public IEnumerator GetEnumerator() + { + foreach (KeyValuePair N in m_Dict) + yield return N; + } + + public override void Serialize(System.IO.BinaryWriter aWriter) + { + aWriter.Write((byte)JSONNodeType.Object); + aWriter.Write(m_Dict.Count); + foreach (string K in m_Dict.Keys) + { + aWriter.Write(K); + m_Dict[K].Serialize(aWriter); + } + } + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append('{'); + bool first = true; + if (inline) + aMode = JSONTextMode.Compact; + foreach (var k in m_Dict) + { + if (!first) + aSB.Append(','); + first = false; + if (aMode == JSONTextMode.Indent) + aSB.AppendLine(); + if (aMode == JSONTextMode.Indent) + aSB.Append(' ', aIndent + aIndentInc); + aSB.Append('\"').Append(Escape(k.Key)).Append('\"'); + if (aMode == JSONTextMode.Compact) + aSB.Append(':'); + else + aSB.Append(" : "); + k.Value.WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode); + } + if (aMode == JSONTextMode.Indent) + aSB.AppendLine().Append(' ', aIndent); + aSB.Append('}'); + } + + } + // End of JSONObject + + public class JSONString : JSONNode + { + private string m_Data; + + public override JSONNodeType Tag { get { return JSONNodeType.String; } } + public override bool IsString { get { return true; } } + + public override string Value + { + get { return m_Data; } + set + { + m_Data = value; + } + } + + public JSONString(string aData) + { + m_Data = aData; + } + + public override void Serialize(System.IO.BinaryWriter aWriter) + { + aWriter.Write((byte)JSONNodeType.String); + aWriter.Write(m_Data); + } + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append('\"').Append(Escape(m_Data)).Append('\"'); + } + public override bool Equals(object obj) + { + if (base.Equals(obj)) + return true; + string s = obj as string; + if (s != null) + return m_Data == s; + JSONString s2 = obj as JSONString; + if (s2 != null) + return m_Data == s2.m_Data; + return false; + } + public override int GetHashCode() + { + return m_Data.GetHashCode(); + } + } + // End of JSONString + + public class JSONNumber : JSONNode + { + private double m_Data; + + public override JSONNodeType Tag { get { return JSONNodeType.Number; } } + public override bool IsNumber { get { return true; } } + + + public override string Value + { + get { return m_Data.ToString(); } + set + { + double v; + if (double.TryParse(value, out v)) + m_Data = v; + } + } + + public override double AsDouble + { + get { return m_Data; } + set { m_Data = value; } + } + + public JSONNumber(double aData) + { + m_Data = aData; + } + + public JSONNumber(string aData) + { + Value = aData; + } + + public override void Serialize(System.IO.BinaryWriter aWriter) + { + aWriter.Write((byte)JSONNodeType.Number); + aWriter.Write(m_Data); + } + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append(m_Data); + } + private static bool IsNumeric(object value) + { + return value is int || value is uint + || value is float || value is double + || value is decimal + || value is long || value is ulong + || value is short || value is ushort + || value is sbyte || value is byte; + } + public override bool Equals(object obj) + { + if (obj == null) + return false; + if (base.Equals(obj)) + return true; + JSONNumber s2 = obj as JSONNumber; + if (s2 != null) + return m_Data == s2.m_Data; + if (IsNumeric(obj)) + return Convert.ToDouble(obj) == m_Data; + return false; + } + public override int GetHashCode() + { + return m_Data.GetHashCode(); + } + } + // End of JSONNumber + + public class JSONBool : JSONNode + { + private bool m_Data; + + public override JSONNodeType Tag { get { return JSONNodeType.Boolean; } } + public override bool IsBoolean { get { return true; } } + + + public override string Value + { + get { return m_Data.ToString(); } + set + { + bool v; + if (bool.TryParse(value, out v)) + m_Data = v; + } + } + public override bool AsBool + { + get { return m_Data; } + set { m_Data = value; } + } + + public JSONBool(bool aData) + { + m_Data = aData; + } + + public JSONBool(string aData) + { + Value = aData; + } + + public override void Serialize(System.IO.BinaryWriter aWriter) + { + aWriter.Write((byte)JSONNodeType.Boolean); + aWriter.Write(m_Data); + } + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append((m_Data) ? "true" : "false"); + } + public override bool Equals(object obj) + { + if (obj == null) + return false; + if (obj is bool) + return m_Data == (bool)obj; + return false; + } + public override int GetHashCode() + { + return m_Data.GetHashCode(); + } + } + // End of JSONBool + + public class JSONNull : JSONNode + { + + public override JSONNodeType Tag { get { return JSONNodeType.NullValue; } } + public override bool IsNull { get { return true; } } + + public override string Value + { + get { return "null"; } + set { } + } + public override bool AsBool + { + get { return false; } + set { } + } + + public override bool Equals(object obj) + { + if (object.ReferenceEquals(this, obj)) + return true; + return (obj is JSONNull); + } + public override int GetHashCode() + { + return 0; + } + + public override void Serialize(System.IO.BinaryWriter aWriter) + { + aWriter.Write((byte)JSONNodeType.NullValue); + } + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append("null"); + } + } + // End of JSONNull + + internal class JSONLazyCreator : JSONNode + { + private JSONNode m_Node = null; + private string m_Key = null; + + public override JSONNodeType Tag { get { return JSONNodeType.None; } } + + public JSONLazyCreator(JSONNode aNode) + { + m_Node = aNode; + m_Key = null; + } + + public JSONLazyCreator(JSONNode aNode, string aKey) + { + m_Node = aNode; + m_Key = aKey; + } + + private void Set(JSONNode aVal) + { + if (m_Key == null) + { + m_Node.Add(aVal); + } + else + { + m_Node.Add(m_Key, aVal); + } + m_Node = null; // Be GC friendly. + } + + public override JSONNode this[int aIndex] + { + get + { + return new JSONLazyCreator(this); + } + set + { + var tmp = new JSONArray(); + tmp.Add(value); + Set(tmp); + } + } + + public override JSONNode this[string aKey] + { + get + { + return new JSONLazyCreator(this, aKey); + } + set + { + var tmp = new JSONObject(); + tmp.Add(aKey, value); + Set(tmp); + } + } + + public override void Add(JSONNode aItem) + { + var tmp = new JSONArray(); + tmp.Add(aItem); + Set(tmp); + } + + public override void Add(string aKey, JSONNode aItem) + { + var tmp = new JSONObject(); + tmp.Add(aKey, aItem); + Set(tmp); + } + + public static bool operator ==(JSONLazyCreator a, object b) + { + if (b == null) + return true; + return System.Object.ReferenceEquals(a, b); + } + + public static bool operator !=(JSONLazyCreator a, object b) + { + return !(a == b); + } + + public override bool Equals(object obj) + { + if (obj == null) + return true; + return System.Object.ReferenceEquals(this, obj); + } + + public override int GetHashCode() + { + return 0; + } + + public override int AsInt + { + get + { + JSONNumber tmp = new JSONNumber(0); + Set(tmp); + return 0; + } + set + { + JSONNumber tmp = new JSONNumber(value); + Set(tmp); + } + } + + public override float AsFloat + { + get + { + JSONNumber tmp = new JSONNumber(0.0f); + Set(tmp); + return 0.0f; + } + set + { + JSONNumber tmp = new JSONNumber(value); + Set(tmp); + } + } + + public override double AsDouble + { + get + { + JSONNumber tmp = new JSONNumber(0.0); + Set(tmp); + return 0.0; + } + set + { + JSONNumber tmp = new JSONNumber(value); + Set(tmp); + } + } + + public override bool AsBool + { + get + { + JSONBool tmp = new JSONBool(false); + Set(tmp); + return false; + } + set + { + JSONBool tmp = new JSONBool(value); + Set(tmp); + } + } + + public override JSONArray AsArray + { + get + { + JSONArray tmp = new JSONArray(); + Set(tmp); + return tmp; + } + } + + public override JSONObject AsObject + { + get + { + JSONObject tmp = new JSONObject(); + Set(tmp); + return tmp; + } + } + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append("null"); + } + } + // End of JSONLazyCreator + + public static class JSON + { + public static JSONNode Parse(string aJSON) + { + return JSONNode.Parse(aJSON); + } + } +} diff --git a/Assets/Plugins/UnityJSON/SimpleJSON.cs.meta b/Assets/Plugins/UnityJSON/SimpleJSON.cs.meta new file mode 100644 index 0000000..ee587a3 --- /dev/null +++ b/Assets/Plugins/UnityJSON/SimpleJSON.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ddc8cb1d4c6f34c00a5593a530efb360 +timeCreated: 1501101606 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/UnityJSON/Util.cs b/Assets/Plugins/UnityJSON/Util.cs new file mode 100644 index 0000000..30df74e --- /dev/null +++ b/Assets/Plugins/UnityJSON/Util.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace UnityJSON +{ + internal static class Util + { + internal static T GetAttribute (MemberInfo info) where T : Attribute + { + object[] attributes = info.GetCustomAttributes (typeof(T), true); + return (attributes == null || attributes.Length == 0) ? null : attributes [0] as T; + } + + internal static Type GetMemberType (MemberInfo memberInfo) + { + return memberInfo is FieldInfo ? + (memberInfo as FieldInfo).FieldType : (memberInfo as PropertyInfo).PropertyType; + } + + internal static object GetMemberValue (MemberInfo memberInfo, object obj) + { + if (memberInfo is FieldInfo) { + return (memberInfo as FieldInfo).GetValue (obj); + } else { + return (memberInfo as PropertyInfo).GetValue (obj, null); + } + } + + internal static void SetMemberValue (MemberInfo memberInfo, object obj, object value) + { + if (memberInfo is FieldInfo) { + (memberInfo as FieldInfo).SetValue (obj, value); + } else { + (memberInfo as PropertyInfo).SetValue (obj, value, null); + } + } + + internal static bool IsJSONExtrasMember (MemberInfo memberInfo, out JSONExtrasAttribute attribute) + { + Type type = GetMemberType (memberInfo); + if (type != typeof(System.Collections.Generic.Dictionary)) { + attribute = null; + return false; + } + + attribute = GetAttribute (memberInfo); + return attribute != null; + } + + internal static bool IsCustomType (Type type) + { + return !type.IsEnum && !type.IsPrimitive && (type.IsValueType + || (!typeof(IEnumerable).IsAssignableFrom (type) + && Nullable.GetUnderlyingType (type) == null + && type != typeof(object))); + } + + internal static bool IsDictionary (Type type) + { + return typeof(IDictionary).IsAssignableFrom (type) || + (type.IsGenericType && type.GetGenericTypeDefinition () == typeof(IDictionary<,>)); + } + } +} diff --git a/Assets/Plugins/UnityJSON/Util.cs.meta b/Assets/Plugins/UnityJSON/Util.cs.meta new file mode 100644 index 0000000..bbcad57 --- /dev/null +++ b/Assets/Plugins/UnityJSON/Util.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a9d540499fd534f9585090cd5623db73 +timeCreated: 1505401304 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..93e08cc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 adragonite + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ProjectSettings/AudioManager.asset b/ProjectSettings/AudioManager.asset new file mode 100644 index 0000000..825ca08 Binary files /dev/null and b/ProjectSettings/AudioManager.asset differ diff --git a/ProjectSettings/ClusterInputManager.asset b/ProjectSettings/ClusterInputManager.asset new file mode 100644 index 0000000..2b2644d Binary files /dev/null and b/ProjectSettings/ClusterInputManager.asset differ diff --git a/ProjectSettings/DynamicsManager.asset b/ProjectSettings/DynamicsManager.asset new file mode 100644 index 0000000..0f724e1 Binary files /dev/null and b/ProjectSettings/DynamicsManager.asset differ diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset new file mode 100644 index 0000000..d767445 Binary files /dev/null and b/ProjectSettings/EditorBuildSettings.asset differ diff --git a/ProjectSettings/EditorSettings.asset b/ProjectSettings/EditorSettings.asset new file mode 100644 index 0000000..7456bea Binary files /dev/null and b/ProjectSettings/EditorSettings.asset differ diff --git a/ProjectSettings/GraphicsSettings.asset b/ProjectSettings/GraphicsSettings.asset new file mode 100644 index 0000000..6abd3c0 Binary files /dev/null and b/ProjectSettings/GraphicsSettings.asset differ diff --git a/ProjectSettings/InputManager.asset b/ProjectSettings/InputManager.asset new file mode 100644 index 0000000..5342fb7 Binary files /dev/null and b/ProjectSettings/InputManager.asset differ diff --git a/ProjectSettings/NavMeshAreas.asset b/ProjectSettings/NavMeshAreas.asset new file mode 100644 index 0000000..a92aa3d Binary files /dev/null and b/ProjectSettings/NavMeshAreas.asset differ diff --git a/ProjectSettings/NetworkManager.asset b/ProjectSettings/NetworkManager.asset new file mode 100644 index 0000000..eef089e Binary files /dev/null and b/ProjectSettings/NetworkManager.asset differ diff --git a/ProjectSettings/Physics2DSettings.asset b/ProjectSettings/Physics2DSettings.asset new file mode 100644 index 0000000..4bb251e Binary files /dev/null and b/ProjectSettings/Physics2DSettings.asset differ diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset new file mode 100644 index 0000000..c33f4c5 Binary files /dev/null and b/ProjectSettings/ProjectSettings.asset differ diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt new file mode 100644 index 0000000..6e4d03d --- /dev/null +++ b/ProjectSettings/ProjectVersion.txt @@ -0,0 +1 @@ +m_EditorVersion: 5.6.1f1 diff --git a/ProjectSettings/QualitySettings.asset b/ProjectSettings/QualitySettings.asset new file mode 100644 index 0000000..50e78ad Binary files /dev/null and b/ProjectSettings/QualitySettings.asset differ diff --git a/ProjectSettings/TagManager.asset b/ProjectSettings/TagManager.asset new file mode 100644 index 0000000..2c3af94 Binary files /dev/null and b/ProjectSettings/TagManager.asset differ diff --git a/ProjectSettings/TimeManager.asset b/ProjectSettings/TimeManager.asset new file mode 100644 index 0000000..0838e4e Binary files /dev/null and b/ProjectSettings/TimeManager.asset differ diff --git a/ProjectSettings/UnityConnectSettings.asset b/ProjectSettings/UnityConnectSettings.asset new file mode 100644 index 0000000..08a6236 Binary files /dev/null and b/ProjectSettings/UnityConnectSettings.asset differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..5b082c6 --- /dev/null +++ b/README.md @@ -0,0 +1,598 @@ +# UnityJSON + +> Customizable JSON Serialization / Deserialization for C# in Unity + +**Table Of Contents:** + +* [Features](#features) +* [Installation](#installation) +* [Serialization](#serialization) + - [Enums](#enums) + - [Serialization Lifecycle](#serialization-lifecycle) + - [Custom Serialization with Serializer](#custom-serialization-with-serializer) + - [Custom Serialization with ISerializable](#custom-serialization-with-iserializable) +* [Deserialization](#deserialization) + - [Deserialized Types](#deserialized-types) + - [Deserialization of Extra Nodes](#deserialization-of-extra-nodes) + - [Deserialization with Inheritance](#deserialization-with-inheritance) + - [Deserialization Lifecycle](#deserialization-lifecycle) + - [Custom Deserialization with Deserializer](#custom-deserialization-with-deserializer) + - [Custom Deserialization with IDeserializable](#custom-deserialization-with-ideserializable) + +## Features + +UnityJSON provides direct JSON serialization / deserialization between +C# objects and JSON strings. Main features of UnityJSON are: + +- Serializes all primitive types, enums, structs and classes into JSON +string directly. +- Deserializes all primitive types, enums, most of the structs and classes +from JSON string directly. +- Supports inheritence during deserialization. +- Supports formatting for enums. +- Supports formatting of serialized / deserialized fields and +properties. +- Supports Unity types (Vectors, Quaternions, Color, Rect, Bounds). +- Provides further customization of serialization / deserialization process. + +UnityJSON works with reflection using C# attributes. The following is a very +simple example using UnityJSON: + +```cs +using UnityJSON; + +public class ParentClass +{ + // Supports structs. + public struct NestedStruct + { + // Supports Unity Vectors + public Vector2 vectorField; + } + + // Supports properties. + public NestedStruct structProperty { get; set; } + + // Supports deserialization to interfaces. + public IList listField; +} + +// Deserialization +var parentObject = JSON.Deserialize( + "{\"structProperty\": {\"vectorField\": " + + "{\"x\":1, \"y\": 2}}, \"listField\":[true]}"); + +// Serialization +parentObject.ToJSONString(); +``` + +## Installation + +Simply use the `unityjson.unitypackage` to add it to your project. + +## Serialization + +You can serialize objects with `object.ToJSONString()` or `JSON.Serialize(object)` +methods. By default, only public instance fields and properties of an object are +serialized, however, you can customize the serialized properties with the use +of `JSONNode` and `JSONObject` attributes. + +```cs +// Include statics in the serialization / deserialization. +[JSONObject(ObjectOptions.IncludeStatic)] +public class AClass +{ + // Serialized because of ObjectOptions.IncludeStatic. + public static int staticIntField; + + // Serialize although it is a private field. + [JSONNode] + private static string staticStringField; + + // Don't serialize although it is a public property. + [JSONNode(NodeOptions.DontSerialize)] + public IDictionary dictionaryField; + + // Serialize this field even if it is null. When serializing + // use the key "customField" instead of "stringField". + [JSONNode(NodeOptions.SerializeNull, key = "customField")] + private string stringField; +} +``` + +`JSONNodeAttribute`s are the main attributes for fields and properties, and define +their serialization / deserialization configuration. The attribute contains options +in the form of `NodeOptions` enum and an optional custom key for the field or property. +The node options only affect the field or property they are bound to with some minor +exceptions when using enumerable types. For serialization, the following node options +might be useful: + +- DontSerialize: Ignores the field / property during serialization. Can be used with +public fields / properties that should not be serialized. +- SerializeNull: By default if a value is null, it is simply ignored and not serialized +(except for enumerables other than dictionaries such as lists or arrays). This option +makes sure that the field / property is serialized anyway. When bound to a dictionary +(IDictionary, IDictionary<,>, ...), this option also affects the values of the dictionary. + +`JSONObjectAttribute`s offer general control over the serialization / deserialization +process. They can be added to classes and structs to inform the serializer where to look +at. It uses the `ObjectOptions` enum which has the following serialization options: + +- IgnoreProperties: Ignores all properties from serialization / deserialization. +- IncludeStatic: Includes static fields and properties in serialization / deserialization. + +### Enums + +Enums are by default serialized directly with their member names. Their serialization +can, however, be customized with the use of `JSONEnumAttribute`. The attribute allows the +following formating options: + +- useIntegers: The enums are serialized / deserialized according to their numeric values. +- format: Optional formatting to be applied given in the form of `JSONEnumMemberFormating`. +It supports lowercase, uppercase or captialize (only the first letter is captialized). +- prefix: Adds an optional prefix to the formatted member name. +- suffix: Adds an optional suffix to the formatted member name. + +```cs +[JSONEnum(format = JSONEnumMemberFormating.Lowercased, suffix = "Position")] +public enum Positions +{ + Forward +} + +JSON.Serialize(Positions.Forward) // forwardPosition +``` + +### Serialization Lifecycle + +You can listen to the serialization lifecycle of an object by implementing the +`ISerializationListener` interface. + +```cs +public class AClass : ISerializationListener +{ + void ISerializationListener.OnSerializationWillBegin(Serializer serializer) + { + Debug.Log ("Serialization started."); + } + + void ISerializationListener.OnSerializationSucceeded(Serializer serializer) + { + Debug.Log ("Serialization ended successfully."); + } + + void ISerializationListener.OnSerializationFailed(Serializer serializer) + { + Debug.Log ("Serialization failed."); + } +} +``` + +The `OnSerializationWillBegin` call is always followed by either the success or +fail call. The fail method is called just before throwing an exception. + +### Custom Serialization with Serializer + +Serializer is the actual component that performs the serialization. The +basic serializer can be accessed with `Serializer.Simple`. When no specific +serializer is given, the default serializer is used (`Serializer.Default`). The +default serializer is the simple serializer unless set otherwise. You can +create your own serializer by simply subclassing `Serializer`. You should then +override the `Serializer.TrySerialize` method to perform your application +specific serialization. + +```cs +public class SpecialSerializer : Serializer +{ + protected override bool TrySerialize ( + object obj, + NodeOptions options, + out string serialized) + { + // obj is always guaranteed to be non-null. + if (obj.GetType() == typeof(MySpecialClass)) { + serialized = MySpecialSerializeFunction(); + return true; + } else { + // Returning false will simply run the regular + // serialization. + return false; + } + } +} + +var obj = new MySpecialClass(); +obj.ToJSONString(new SpecialSerializer()); +``` + +You can then pass this new serializer as an argument in the `object.ToJSONString` +method or set it as the default serializer to be used automatically. The classes that +are serialized with the `Serializer.TrySerialize` method do not receive serialization +lifecycle calls from `ISerializationListener`. + +### Custom Serialization with ISerializable + +Another way to provide custom serialization is by implementing the interface +`ISerializable`. It is important to notice that `Serializer.TrySerialize` is called +first and this interface will be ignored if that method returns true. + +```cs +public class AClass : ISerializable +{ + string ISerializable.Serialize(Serializer serializer) + { + return "{" + serializer.SerializeEnum (MyEnum.Value) + "}"; + } +} +``` + +The classes that are serialized with the `ISerializable.Serialize` method do not receive +serialization lifecycle calls from `ISerializationListener`. + +## Deserialization + +You can perform deserialization by calling `JSON.Deserialize\(jsonString)` method. +This will instantiate a new object of that type and fill it with the data from the +JSON string. If you wish to use a previously created object, you can also use one of the +`JSON.DeserializeOn(obj, jsonString)` or `obj.FeedJSON(jsonString)` methods. This object +can either be a struct or a class. + +Deserialization can be more cumbersome then it's counterpart as the serialization of an +object is unique whereas the deserialization of a JSON string can produce different +results. The basic JSON types allow the following deserialization approaches: + +- JSON string: System.String (string) +- JSON bool: System.Boolean (bool) +- JSON number: System.Int32 (int), System.UInt32 (uint), System.Byte (byte), +System.Single (float), System.Double (double) +- JSON array: System.Array, IList, IList\<\>, List\<\> +- JSON dictionary: IDictionary, IDictionary\<,\>, Dictionary\<,\>, custom classes and structs + +UnityJSON aims to support a lot of the main system classes such as lists and dictionaries. +However, not everything is supported directly by framework, for instance LinkedLists are +not supported for deserialization at the moment. + +The framework decides the type to deserialize into from the type of the field or property. +This is important as the deserializer needs to instantiate an object from the given type +only. If an interface such as `IList` is used, then a default instantiated object type is +defined, `List<>` is used for instance for `IList`. + +`JSONNode` and `JSONObject` attributes are also used for deserialization and provide +more options. + +```cs +// If an unknown key is received during deserialization, simply +// ignore it instead of throwing an exception. +[JSONObject(ObjectOptions.IgnoreUnknownKey)] +public class AClass +{ + // Automatically ignored at deserialization as the property + // does not have a setter. + public float propertyField { get; } + + // Deserialize although it is a private field. + [JSONNode] + private string stringField; + + // Don't deserialize although it is a public property. + [JSONNode(NodeOptions.DontDeserialize)] + public IDictionary dictionaryField; + + // Don't throw an exception even if the deserialized object + // doesn't match the type (Vector2 expects "x" and "y" keys). + [JSONNode(NodeOptions.IgnoreTypeMismatch)] + public Vector2 vectorField; + + // Don't throw an exception even if the deserializer cannot + // instantiate an object for the type. This can for example + // happen for classes without a default constructor. + [JSONNode(NodeOptions.IgnoreUnknownType)] + public ClassWithConstructor classField; + + // Even if the JSON object has a "customField" key, don't + // assign it if it is null, simply ignore it. + [JSONNode(NodeOptions.DontAssignNull, key = "customField")] + private string stringField2; +} +``` + +Deserialization process tends to throw a lot of `DeserializationException`s exceptions in +case of any problems. By default, the exception is thrown in the following scenarios: + +- The JSON string is not applicable to the target type. For instance the target type is +a string and the node contains a boolean value. This is called a type mismatch error. You +can ignore it with the `NodeOptions.IgoreTypeMismatch` option. +- The target type cannot be instantiated or is not supported. This is called an unknown +type error. You can ignore it with the `NodeOptions.IgnoreUnknownType` option. +- The JSON node cannot contain unknown keys that cannot be mapped to the fields and properties +of the class / struct. In such a scenario an exception is thrown, this is called an +unknown key error and can be ignored with `ObjectOptions.IgnoreUnknownKey` option given +in a `JSONObjectAttribute` to the class or struct. Another option to ignore this error is +by using an extras dictionary (see [Deserialization of Extra Nodes](#deserialization-of-extra-nodes)). + +`NodeOptions` also offer the following other options for the deserialization process: + +- DontDeserialize: Ignore the field / property from the deserialization process. Similar +to `NodeOptions.DontSerialize` for serialization. +- DontAssignNull: By default if a key exists in the node its value is assigned to the +associated field or property automatically even if it is null. This option makes sure +that the null assignments are simply ignored by the deserializer. This can be particularly +useful to use with `NodeOptions.IgnoreTypeMismatch` because when the type mismatch errors +are ignored, in case of a type mismatch the deserializer always returns null. For +primitive types, null is mapped to the intricate default value (0 for int, false for bool). +By using this option, you can prevent the deserializer from assigning the default values +upon type mismatch errors. +- ReplaceDeserialized: The deserializer tries to reuse the previously created objects +when deserializing. If a field is of type T and it is instantiated, when the deserializer +recevies values for this object of type T, it simply assigns them directly to the already +existing object. This option forces the deserializer to instantiate a new object instead +completely build from the data recevied. This option can only be used for custom classes +that do not implement IEnumerable interface. An example can be seen below: + +```cs +public class A +{ + public int intField; + public float floatField; +} + +public struct B +{ + public A a1 = new A(); + + [JSONNode(NodeOptions.ReplaceDeserialized)] + public A a2 = new A(); +} + +B b = new B(); + +// Without ReplaceDeserialized +b.a1.floatField = 2; +B deserializedB = JSON.Deserialize("{\"a1:{\"intField\":1}}"); +deserializedB.a1.intField // 1 (from the deserialization) +deserializedB.a1.floatField // 2 (from the previous assignment, object reused) + +// With ReplaceDeserialized +b.a2.floatField = 2; +B deserializedB2 = JSON.Deserialize("{\"a2:{\"intField\":1}}"); +deserializedB2.a2.intField // 1 (from the deserialization) +deserializedB2.a2.floatField // 0 (new object of type A is instantiated) +``` + +### Deserialized Types + +The deserialization is currently defined for the following target types: + +- int, uint, byte, bool, float, double +- Enums (see [Enums](#enums) for fomatting details) +- T[]: Type T must be supported +- System.Array: Instantiates object[] +- List\: Type T must be supported +- IList\: Instantiates List\ +- IList: Instantiates List\ +- Dictionary\: K must be primitive, enum or string. V must be supported. +- IDictionary\: Instantiates Dictionary\ +- IDictionary: Instantiates Dictionary\ +- Custom classes or structs: Must have a default constructor without arguments. + +The type `object` is also supported and the deserialization for this target +is performed according to JSON node at hand with the following mapping: + +- JSON string: string +- JSON number: double +- JSON bool: bool +- JSON array: object[] +- JSON dictionary: Dictionary\ + +You may, however, want to restrict the supported types and/or support custom +types too. You can use the `RestrictTypeAttribute` for that. This attribute takes +`ObjectTypes` enum value and an optional custom types array. The `ObjectTypes` +enum define which types to look for, the default value is `ObjectTypes.JSON` +and supports all types except custom. If the custom types are supported (by +either using `ObjectTypes.All` or `ObjectTypes.Custom`) then an additional +array of custom types can be given to try deserializing the object into them. +Custom types are classes or structs cannot be enumerable and nullable. The order +of the custom types are important as they are tried one by one. If no type can +be deserialized into, then a generic dictionary is created (unless the dictionary +type is not supported, in which case an exception is thrown). + +```cs +class A +{ + public int intField; +} + +class B +{ + public float floatField; +} + +class C +{ + [RestrictType(ObjectTypes.Custom, customTypes = new Type[] {A, B})] + public object field; +} + +var c = JSON.Deserialize("{\"field\":{\"floatField\":5}}"); +c.field // object of type B with floatField 5 +``` + +`RestrictTypeAttribute` can also be used with IList, IList\, List\, +object[], IDictionary, IDictionary\, Dictionary\. + +### Deserialization of Extra Nodes + +Sometimes you may receive more key / value pairs than what your class and struct supports. +By default, the deserializer will throw an exception in this case unless +`ObjectOptions.IgnoreUnknownKey` is used. You can, however, also decide to parse these +extras into your class / struct. You can do that by adding a field or property with +`JSONExtrasAttribute`. The type of the field or the object must be Dictionary\. +The attribute can also have optional `NodeOptions`. A field or a property with this +attribute is never serialized or deserialized even if it is public. + +```cs +class A +{ + [JSONExtras] + public Dictionary extras; +} + +A a = JSON.Deserialize("{\"key\":5}"); +a.extras["key"] // 5 +``` + +You can also use `RestrictTypeAttribute` to restrict the supported types or use custom +types. The extras are also used for the serialization and are serialized on the same level +as the object. + +### Deserialization with Inheritance + +You may want to provide deserialization to interface or abstract class targets. One option +would be to use a custom deserializer +(see [Custom Deserialization: Deserializer](#custom-deserialization-with-deserializer)), however +in most cases you can also simply do that by using `ConditionalInstantiation` and +`DefaultInstantiation` attributes. These can redirect the instantiated types for an +interface or a class. `ConditionalInstantiationAttribute` checks for a key value pair in +the received JSON node and instantiates the referenced type if there is match. If no +condition is met, then the referenced type from `DefaultInstantiationAttribute` is +instantiated. If no such attribute exists, then the default framework deserialization +is performed. + +```cs +[ConditionalInstantiation(typeof(A), "type", 0)] +[ConditionalInstantiation(typeof(B), "type", 1)] +[DefaultInstantiation(typeof(C))] +interface I +{ +} + +class A : I +{ + public int type; +} + +class B : I +{ + public int type; +} + +[JSONObject(ObjectOptions.IgnoreUnknownKey)] +class C : I +{ +} + +I obj = JSON.Deserialize("{\"type\":1}"); // obj is of type B. +``` + +### Deserialization Lifecycle + +You can listen to the deserialization lifecycle of an object by implementing the +`IDeserializationListener` interface. + +```cs +public class AClass : IDeserializationListener +{ + void IDeserializationListener.OnDeserializationWillBegin(Deserializer deserializer) + { + Debug.Log ("Deserialization started."); + } + + void IDeserializationListener.OnDeserializationSucceeded(Deserializer deserializer) + { + Debug.Log ("Deserialization ended successfully."); + } + + void IDeserializationListener.OnDeserializationFailed(Deserializer deserializer) + { + Debug.Log ("Deserialization failed."); + } +} +``` + +The `OnDeserializationWillBegin` call is always followed by either the success or +fail call. The fail method is called just before throwing an exception. + +## Custom Deserialization with Deserializer + +Deserializer is the actual component that performs the deserialization. The +basic deserializer can be accessed with `Deserializer.Simple`. When no specific +deserializer is given, the default deserializer is used (`Deserializer.Default`). +The default deserializer is the simple deserializer unless set otherwise. You can +create your own deserializer by simply subclassing `Deserializer`. You should then +override the `Deserializer.TryInstantiate` and `Deserializer.TryDeserializeOn` methods +to perform your application specific deserialization. These two methods are +independent of each other and represent the two steps of deserialization: +instantiation of the object and feeding the JSON string inside. You can decide to +override only one method too. + +```cs +public class SpecialDeserializer : Deserializer +{ + protected override bool TryInstantiate ( + JSONNode node, + Type type, + NodeOptions options, + out object instantiatedObject) + { + if (type == typeof(MySpecialClass)) { + // Custom deserializers can be used to instantiate classes + // with constructors. + instantiatedObject = new MySpecialClass(node["key"]); + return true; + } else { + // Returning false will simply run the regular + // instantiation process. + return false; + } + } + + protected override bool TryDeserialize ( + object obj, + JSONNode node, + NodeOptions options) + { + // obj is always guaranteed to be non-null. + if (obj.GetType() == typeof(MySpecialClass)) { + MySpecialDeserializeFunction(obj, node); + return true; + } else { + // Returning false will simply run the regular + // deserialization process. + return false; + } + } +} + +var deserializer = new SpecialDeserializer(); +var obj = JSON.Deserialize(jsonString, deserializer); +``` + +When the `Deserializer.Deserialize` method is called, it first tries to +instantiate the object with the `Deserializer.TryInstantiate` method, if +that fails, then the regular type based instantiation is performed. When +the object is instantiated, the `Deserializer.DeserializeOn` method is called +on the object. This first tries the custom `Deserializer.TryDeserialize` +method and then performs the regular framework deserialization if that fails. + +The classes that are deserialized with the `Deserializer.TryDeserialize` method +do not receive deserialization lifecycle calls from `IDeserializationListener`. + +## Custom Deserialization with IDeserializable + +Another way to provide custom deserialization is by implementing the interface +`IDeserializable`. It is important to notice that `Deserializer.TryDeserialize` is called +first and this interface will be ignored if that method returns true. In addition, +you still need to make sure that your class can be instantiated. + +```cs +public class AClass : IDeserializable +{ + void IDeserializable.Deserialize(JSONNode node, Deserializer deserializer) + { + listField.AddRange (deserializer.DeserializeToList(node["list"])); + } +} +``` + +The classes that are deserialized with the `IDeserializable.Deserialize` method do +not receive deserialization lifecycle calls from `IDeserializationListener`. diff --git a/unityjson.unitypackage b/unityjson.unitypackage new file mode 100644 index 0000000..2ae240b Binary files /dev/null and b/unityjson.unitypackage differ