From d68270428fc2d8df481821feb64446d1db39c9e8 Mon Sep 17 00:00:00 2001 From: Alihan Livdumlu Date: Sat, 16 Sep 2017 15:16:50 +0200 Subject: [PATCH] create UnityJSON package --- .gitignore | 36 + Assets/Plugins.meta | 9 + Assets/Plugins/UnityJSON.meta | 9 + Assets/Plugins/UnityJSON/Attributes.cs | 323 ++++ Assets/Plugins/UnityJSON/Attributes.cs.meta | 12 + Assets/Plugins/UnityJSON/Deserializer.cs | 1063 ++++++++++++ Assets/Plugins/UnityJSON/Deserializer.cs.meta | 12 + Assets/Plugins/UnityJSON/Enums.cs | 335 ++++ Assets/Plugins/UnityJSON/Enums.cs.meta | 12 + Assets/Plugins/UnityJSON/Interfaces.cs | 95 ++ Assets/Plugins/UnityJSON/Interfaces.cs.meta | 12 + Assets/Plugins/UnityJSON/JSON.cs | 127 ++ Assets/Plugins/UnityJSON/JSON.cs.meta | 12 + Assets/Plugins/UnityJSON/Serializer.cs | 487 ++++++ Assets/Plugins/UnityJSON/Serializer.cs.meta | 12 + Assets/Plugins/UnityJSON/SimpleJSON.cs | 1432 +++++++++++++++++ Assets/Plugins/UnityJSON/SimpleJSON.cs.meta | 12 + Assets/Plugins/UnityJSON/Util.cs | 66 + Assets/Plugins/UnityJSON/Util.cs.meta | 12 + LICENSE | 21 + ProjectSettings/AudioManager.asset | Bin 0 -> 4140 bytes ProjectSettings/ClusterInputManager.asset | Bin 0 -> 4104 bytes ProjectSettings/DynamicsManager.asset | Bin 0 -> 4280 bytes ProjectSettings/EditorBuildSettings.asset | Bin 0 -> 4104 bytes ProjectSettings/EditorSettings.asset | Bin 0 -> 4168 bytes ProjectSettings/GraphicsSettings.asset | Bin 0 -> 4390 bytes ProjectSettings/InputManager.asset | Bin 0 -> 5520 bytes ProjectSettings/NavMeshAreas.asset | Bin 0 -> 4460 bytes ProjectSettings/NetworkManager.asset | Bin 0 -> 4112 bytes ProjectSettings/Physics2DSettings.asset | Bin 0 -> 4380 bytes ProjectSettings/ProjectSettings.asset | Bin 0 -> 52226 bytes ProjectSettings/ProjectVersion.txt | 1 + ProjectSettings/QualitySettings.asset | Bin 0 -> 4976 bytes ProjectSettings/TagManager.asset | Bin 0 -> 4308 bytes ProjectSettings/TimeManager.asset | Bin 0 -> 4116 bytes ProjectSettings/UnityConnectSettings.asset | Bin 0 -> 4216 bytes README.md | 598 +++++++ unityjson.unitypackage | Bin 0 -> 23552 bytes 38 files changed, 4698 insertions(+) create mode 100644 .gitignore create mode 100644 Assets/Plugins.meta create mode 100644 Assets/Plugins/UnityJSON.meta create mode 100644 Assets/Plugins/UnityJSON/Attributes.cs create mode 100644 Assets/Plugins/UnityJSON/Attributes.cs.meta create mode 100644 Assets/Plugins/UnityJSON/Deserializer.cs create mode 100644 Assets/Plugins/UnityJSON/Deserializer.cs.meta create mode 100644 Assets/Plugins/UnityJSON/Enums.cs create mode 100644 Assets/Plugins/UnityJSON/Enums.cs.meta create mode 100644 Assets/Plugins/UnityJSON/Interfaces.cs create mode 100644 Assets/Plugins/UnityJSON/Interfaces.cs.meta create mode 100644 Assets/Plugins/UnityJSON/JSON.cs create mode 100644 Assets/Plugins/UnityJSON/JSON.cs.meta create mode 100644 Assets/Plugins/UnityJSON/Serializer.cs create mode 100644 Assets/Plugins/UnityJSON/Serializer.cs.meta create mode 100644 Assets/Plugins/UnityJSON/SimpleJSON.cs create mode 100644 Assets/Plugins/UnityJSON/SimpleJSON.cs.meta create mode 100644 Assets/Plugins/UnityJSON/Util.cs create mode 100644 Assets/Plugins/UnityJSON/Util.cs.meta create mode 100644 LICENSE create mode 100644 ProjectSettings/AudioManager.asset create mode 100644 ProjectSettings/ClusterInputManager.asset create mode 100644 ProjectSettings/DynamicsManager.asset create mode 100644 ProjectSettings/EditorBuildSettings.asset create mode 100644 ProjectSettings/EditorSettings.asset create mode 100644 ProjectSettings/GraphicsSettings.asset create mode 100644 ProjectSettings/InputManager.asset create mode 100644 ProjectSettings/NavMeshAreas.asset create mode 100644 ProjectSettings/NetworkManager.asset create mode 100644 ProjectSettings/Physics2DSettings.asset create mode 100644 ProjectSettings/ProjectSettings.asset create mode 100644 ProjectSettings/ProjectVersion.txt create mode 100644 ProjectSettings/QualitySettings.asset create mode 100644 ProjectSettings/TagManager.asset create mode 100644 ProjectSettings/TimeManager.asset create mode 100644 ProjectSettings/UnityConnectSettings.asset create mode 100644 README.md create mode 100644 unityjson.unitypackage 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 0000000000000000000000000000000000000000..825ca080928058396683a7908f7322d444bf0ef4 GIT binary patch literal 4140 zcmeH~y-wUf6os#M1BCG3q)Fofr9vyM5Kywf5+M{MSc`38Fk@tx-C23<0xH+r(}L2Z zOG8V=14Ma)yg^<8bB1yu%bZ5r*$jykC8KVG>f-@)$2 z^*mOJ_mclAbCCtYmdI9i9@_)eR~I^w-bvS*tf!@8jj>VW2ca@r+IBE7I`O}$Ftv%a zb)+(r`oTb}E8qiruCX;xJ#=+c%Ay?{Zf8-XlOVof-HnqpQ>JUXe2b=@%?LfYXXJr}HTfDphg~Bow^A@M&-I8nT>}w lZpH2NSpSn{(ycP>6#eQmh@BKHN+iT6Zw5>Gsc zuGFvA8$pA9rfKRp0N0qN>EzbMmvb}I>E^_<>&!783O&wY#r1jL5w8`TbK~_h1wT0C4qZTe8P5F= zWt{h}&=>S?Va+`b!}ED6RL4APSWgsQ&dKvU0OR^2a5g>{^M{C;R~f=e{uD9uqYPop zdEe)Vne$9+C4Y;U`6NRa^HtP;%3`A986EMj1!w)(%K9H!T;Ae{PvJ0p9$YZdV$MI~ zyfr|+uT#LBhdxdN`?51XJ>+gSe+}>OoYP~R?-x2`{7k{UPCp6#q%9(`BQ-tVyrV@g zG=18mK0P7NGkPJfX$vEDDAY|9Ez3Z3l_?QOD^zl28G$EECkvM1Zsg02j+RL$R@+Fe zD=Bx!LD8leM@IN&EeH~6M%~wA)suP!y9yJ=QSxWzzErV~QGctr*Ts-Bxf_}#;Xew5 zFZsO@-O%9<4$AO$B({{iwJml{*psbT`*KLVW*hOU=u6#`KrSD>O|MiXP{@en8L-RKx{yDRF J@b}^Wv|reCHJAVZ literal 0 HcmV?d00001 diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset new file mode 100644 index 0000000000000000000000000000000000000000..d767445aac8b60ef4f46ef1aef3576d2724be49c GIT binary patch literal 4104 zcmeH@%Syvw5QR^gw%)JkN?eHx5lX96S3(gAE)a$gac$YfcbF%3ycgvxv*0xU6$};jPX2ztH?QmJuG2Gg*)EWB#MmMQJU)*;(kBn8HfUAfJ$#Zcu~xsGxBVHf#2s>R zpWk7*k1u8`oY5Y+%!7|r#%q34e@-wJ&*G5T8cW^F(^2X31Mb*lss1tXR6I9wS7LY- z@%+f$332OroN!WiRZsr$EI!DJtfkEqUB>-Zhca{h`?Mf!$oH6w&j`|nyvkgBR**L2 zN6f|N1ZmYUPHyk_dvQCgMM)BOT3y_n*P2l$>cR#@o#3J!g}4inE3|rXh%iWkvtpwk tOM|#Ih}#Vwvo;7emoCnn0!{&^fK$LJ;1qBQI0c*nP64NYQ{aCq@C%#jVbcHr literal 0 HcmV?d00001 diff --git a/ProjectSettings/EditorSettings.asset b/ProjectSettings/EditorSettings.asset new file mode 100644 index 0000000000000000000000000000000000000000..7456bea7c147f0fbf7ee4f7cf27d35dfe67d2541 GIT binary patch literal 4168 zcmeH~yN=U96o!x2-0u*eK}h=qK`DSDO|%zSil8hOD^a1rK89HwPc)ve5e@Em3_3a* z9)yOThGr23Z-6ChV4+fzGj3NV~%KC7^o}^>qYw{hhY;D3H9o_yf{^gDS zCs{-PlH@*p_}8b8r`V@254h!x{ufz8|C;1J{Q>GHIMen2I(Qx3{%^E=JJHVAh@I2< z%Yx?5w^xpBuChJ0g*N%7$(=P>v|KEVb+R9^)hg3(m6P46Nm$qxr)rtG4SuEGXk%L% zEv$AtQ}Ju=Lo<~m@|!brz*aP7*6efa9&*mM)_#=7*)rh-4drMB-ThPb#2EKj&AC{p z7_B@l*tQy+wAIlvP4%j_q4|>kj)51+5AT3?z&qd_@D6wfyaV0=?|^r}JMh0cfcxK` lF7%6x*Qe~%`U9PD(d`!ZJ={ax%8gg^Y@Ft994E&$e*ku;#qR(B literal 0 HcmV?d00001 diff --git a/ProjectSettings/GraphicsSettings.asset b/ProjectSettings/GraphicsSettings.asset new file mode 100644 index 0000000000000000000000000000000000000000..6abd3c0946cff7638e7eea1dfe8d3ee0aa04709a GIT binary patch literal 4390 zcmb`KO^g&p7>28tSrkPP{6RznQ9%%wKS5B0{byHpaM;cFqq{Eg?bW4*-fOM<4%%egb6Q?^gT_6&w_D!5&vzs- zdUfx#Tcso01s4y7ySFbw5X&x|>mGdEI+8?$O<1dABZ) zzuwPPB73g3TCR1kqD|dxD73up{Tlb^ZnwN!_kkov?|L7!T3j|*l)QUBk0rjE zXV1eS%N;S+Cpud&X|C(AjiaA$>GKNDOS7)Nu5}%;+z~T~bJ)b;UeVJ0rg5G~nC98} zJZZU;>}L^ixc|pcXna2Zik#F;{HGGSEs1{xIk{;3kLUOshM&msHw_=OTcEe9;Z23OJr*iy=;l&JxPv;ak zqo^&seVWdmWtnFiQ^O zxnh3*H2kc_maoB!o&L{eIOo3=oa^O#o5ncg2MvE-W6O(%zmVe%!(YttONPIc;~yFR za*kg${FNO4*6_I;|IzSQ$p`iPd7gg*=XsXo9rEtyQpNJ_^IRo|@wxODV(>gOPK~fb zUJw(AzZRQGt}O5Gx-awK&LI6)CwWQlOPxF^s|C(Rhw<5NSY8mwU1vSw^ZnwY2(b-g z`o$ISjo|pld4a9)O@^P(@y&*NIljg4rsahs{#L_%a2nT3-93f}goEVzoE!yrfLi3R zl+PIcn#ST}C|eD;8kJQXyM8?q!)@0?t=y0`6@a~eaKsoOVOlX_1ZO6({rPLDO6s7g;|tY z#aS7;(vL?1&&)DeRx5O46=6oQ?ZrhID>!+GG}4aZpgFC4>{7{XDbMvOJQGSkYGD$7 zb*UVLak5ptcxcg$#G+UduVah}u45qw+_)873@X^seCLdtNlsUzImZmvW@K1bF=qr% z!H;~V$7(Ki5gs=HKy}w)WT#`X{e19$=I!8kyAV=P1!2Pq1$Rv6Gt_uXPc8gH=v5XEKLWV zQmR$*r0Zvbk&U|HNYJbx{k$4k@6jb+Hr?vK4$@u!Jyu@{t}Yj}+^)o-#4WnOd$Ew;KE2hbYwDykNg7i3&*O#1UdB=SUHx=K_752qp&bx{#gG+lqHjSuV# z^yFfuq1PJCMoP<1`mypOo*1(Y?80pd!Wq?UsZhpk+)X}{SW?@OX=JKGtV=!c9jYj3 zhgB8rZTk?ItLSIcoLabzD(AqG{(ef^E5B=W|De%I`}OZX-AAT>C$6+te*b8u z-ybXed@s>fqj3Dn|Hskbk5^ctm8th->dx*JpADv-j_J#^wO9VLb0S?wzQ;+;cKicO C-@AGM literal 0 HcmV?d00001 diff --git a/ProjectSettings/InputManager.asset b/ProjectSettings/InputManager.asset new file mode 100644 index 0000000000000000000000000000000000000000..5342fb7f6bee4d48624dff0508f686c868478b6e GIT binary patch literal 5520 zcmeI0%Wl&^6ow}>E%#fY7g`GCepyr$1V~7LTS`?Btw0qw2?@E$v~@}B$aZ=Yi%4v` zf^K*KgwzdU(+xYGpokS)gv5@AApHM0jx&kV?vYP4nmPZ>`DT1Pjyx8kH6=vHsu1F! z5C>2RadqI@z~J}eE<_0- zW#yEFNa45EB1-EiQA$H3Dkv%O13IxBuj1ACZK**pSg5z4Unspr{6wo4G32n&TJSrJ zdJcUY-%7E>InrVbV?SYD>TOlMRpW1mo-#PTP2=yNu1cGD1S0wLcUJX)e+)kPyHE%7 z>ipdtZ#44vP*+9gGarE-@CV?NzYldBe?P|?jeOq4I6iK$#vg@Gemm+o{vnPx8u`q5 z9KWNg2kUH&Wq zKKZ=Qas1;PZ#44zsjH&%PgM1^_yFPx{4~l*6dLy}bv}&LPf>)0`g^pgpQZ?-{s7|< zqvkuK)JR{e=x3=LfzIdXGhA}cq0q>ozK%Ba^AusB{snF77bwE+*@dD%ZrbLIbVdH< z(4zDLZD`T*L{@qk*DCndyqqxeQuF1ES=H@|GhFn2#})<0v)EU0&741>4}kJeA2Z{c z^OnDa8O*Hkq^$rHJliY?%bu66FZ`u~6ebro=PXSW^D0xkw#Zi1A4lXTtt10U29gXU z8AvjaWFW~vl7S=x|8oX-{^J>T%yF${$M(%!bhUi%&&g?@ugI=7GwY+~yR%uTv#4>n zFr4-`H(1xcSF?v_=_hy(w=xlx%&fCu^W0x71UwII#x9_2#^X9hEmz_jfo43LbDjMe z-wl_~^L^eadb0l#TFWEB8ka}3KsX`ua$N`4u9!G_uq)zm_N{qW7*u1-vEq5N*0dtl zGC6y5CUbVbm`AoEA(~bjA2Rs; zA(($M<2t$A(^)CQd!+@}IkhbHtqA?i_G?|Ja$md~zJ=rMZ8~8*+i`o7dc9TSaUqNk zuP59$^?Q-miNmIcP5-KYN*!<24ve_X?@`FC--~ffecr8^_4_f7sn6S-8Mhzfc>0{p zdHhByNU1jD-TV497He>iFz}yJtU$bCIL?(o!xdbab{=PnH3)(;|K8W zK@br<=s_^xK|FYsoJG8N^d$NXvi|=)J!@t^f=q=@SN*!Wr>eRR+aks9BKtkTKaqWy zL@thA8XdbcCY$Yc+i?IMleXKxKW)^0eHgm+;@$YiN6$Y8XF$(ly@$(?%OWwhw2cvq zIQV;AkodhLaS@P5CBu=g;74K8-|RR3J}Ii-r+@g33dTCW7&a0VZMwL zbufIVp$TWcbFdlDt`5)UySu})`R=j22l*~TXFTJW_jde?=DRLz#*^G4znjF_vK$v| zB;`*mKhVXWT7IyLKePN$7k_2>;V!;z`H?RE#d6+1h4HX%-=H)8V}y`2&Y#f9`w1bb z{~ze&1B8&2Z$c*@B!r~A50^+jWGH#khyCEZ7xt3GU(c#&9877CY57}>NAC3^l z=DV4GI|-chTPf3Tr^wAHW%}*3<@4&^bserQG=nu=iI#K}x%FBos;N~dc5wZU){gj@*7i?W)OyDH;h!hqQ!UXi@N3pbw(?`s<)IkqdjlQ zeTbM-CE?4irwxx%MO7-Tri%1}rz+Yd{m0f{I6_j!1IU}MWO jg85F0j89{-xtj`riM)Mt?uxPO22QN&Yzt4Cz^(oTvoRh= literal 0 HcmV?d00001 diff --git a/ProjectSettings/NetworkManager.asset b/ProjectSettings/NetworkManager.asset new file mode 100644 index 0000000000000000000000000000000000000000..eef089ee975c36cb94ff81349ee780e719ed4d1d GIT binary patch literal 4112 zcmeH_%Syvg5QgWZsoHw0YXz6$CK#_PkwQ@lUJ42>r06Lr40o`VNXa!T3*dh^22JoCA}Y`S>$u@)II!ohX+hqA6q~q7`S=aceI9gkdNIz&+D2 zyv=mq?ml0NPdD?uZ|_U6x`AK`pEaTYuY_^OT+m~Rf@eX24amAU1ieE$iJrNI;KTYJ z_zY_1DTX-AKfvcu^ZdVAmTr>JEY23})p*}uqQE&!5-!YtUn_j^l5d2Le z2~S7+uRE9SnZk_3iADcmDXOwEfd3uYbB- zIu3e%jA)#vVNVmKSTe|hl*q%+=i`)q=}~$OAeYj^qo2WVLA)CSC18`78>3Cs;LUKp zuDinz8qRY%-@_`RXE1KSxE6zr>->s3C*gDMu8e73f^HKr>t2_0FUx-kG3WCf8}Fa% zyb7QB^$cOmd7p2=XTFCa%<_-nGvCV)X88s9%z3YDmT$vnj)Ta|@?Q`!-=}e&|EJ+M zIh@am^Ytn2)IVrA4mdMg|ES@74s4cJ4bP6fz+W`{wk%uc*NIXEj$gXn;cpv$hr>4w zztiDchTrAz%ZA?#j;oaIlXZVL{2qt@ZTJC)@54pI6!HD?=5?J-)HwvseY>}ie+GO8 zoO{Xl&ZEyT^!+?(^6zta-SA0=KX3RUhc^trKj$8??rCt=J)HAl-3J_Qbsu!N)qTj} zR`=nYYu%pFeI)0@xy{jDb&omR>OSFct2>o* zkAA>=;6v#0`)WGle8yuQa#9}5pUnAq_8vHZHGEH=DsX1ECt z8ANTATEs$;_)*B^8&T}DKLl-NtEXh#i-NOAJS8^jekg^iMoP&xuhFy;cGrTdaZ8CX z4l?E^R)C{CtMXKIT7PX=P z&lI+~g4xei+_b0~g$YV7bvm(3kp99N+9>1a{G_G)ZdWSqih<1pTU!jPQ4siN^-Q%V z!mey4(P~^3L9i^^=XgQxFNHNf7R%YQRDyNUkDI+{y;z|b+eaxfvq7#}#Ffgz0t@Nw zSCop@ZCh;h%DOlw5k!gc>aU;_m{8ym6YFmcW?R9#rbkE{(5T*^-^9BOUoC& VI0m0%&E8DYxk=3M|KR_n{R@4nC%OOt literal 0 HcmV?d00001 diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset new file mode 100644 index 0000000000000000000000000000000000000000..c33f4c5613d9793f058eaaef90bd50aad2450439 GIT binary patch literal 52226 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..50e78adadce926b088dff7930adcb260488b82f0 GIT binary patch literal 4976 zcmeI0%WoS+9LImon>IipP$&iB@_q&qrywCUI8Kys2#uR{5^mMlJ5Hur?^^pvogR?6 zpd$4E7o@@gdf`wwRpP_}I3lG-#0d~WTwD4NV1D1(*|pc?05=YvcC<5}`Mu`1GtSHs zjc*c7HHqjxqGS9ZIz98+%xry@{_1o(xg3L6Ks%j}?tD8v^3CLxH%?ru+$hW)JI(ay z5YZ@xxx7x)W|fZaw25;3?~7q--^fvWgdt4DHAla&EWClU30)xnBg>!S9_$Q*wxKhW zo8=ZNICNq@tnHj+9k3*hAsVIv_b(Xvu~dG9-eTDj_hDx|<)az<6U=i(_#MmGzYAQ~ zF*?V#VITb?DId}DtK3ICN4cWpxQF>}a~pgTkW0ePa~u3_KrRW#O98(JkW0ef<2Lxc zfLy?@5w$<$HhBCNyZkf5`338e$bW74{XP5-&d(3rM~qXN=h^A41XNF*o`w_4!_gjT#|ggYj|GMuKfFkpX%cu8~#Ke|H|+u`}p^UKLuWl z?PI_E#2oASY4CQN}9a=l&;WMbXu%vnvIfsr(9K4vZ2r%P0Iq-$?W1Q$>!TVdqOWOl+;ffxE- zQ`IW*NI=33)uN+BzzLzitA}ObhpOfvI__=B9T^H8yDxAM{$OFU@5BCrEjTAl4PsqM7Fdm!e>p+U0C=023~JjB9*$kEbAh2 z(l^a38*8Oon);7dw?AkNGBC)%AOnL83^Fjtz#s$vrwriUj5{*!!z+Abl0ldW59gZr zY|~uii%0Rx>(>`PxP1A{&4aat+qZ5lbjLGl^vTt$3m`Z%51D7uhaa?)(zWsVp+2C0 zSh;y{pnc%Z+v@}Ku~eh!$T)V`NBOsY68S`@P$;~Kc9CnZAIxpl^K3BG#6H?Y`4VgV zBVX}Yl2a&g(eiEW14bYN+bH<%P?CZ#0rkk?Hjb7UM}=N&?@Ead@s-#}qs@Ga)Cy>S3Q{`Rl MGnsF=w{D^RH!SJ_(f|Me literal 0 HcmV?d00001 diff --git a/ProjectSettings/TagManager.asset b/ProjectSettings/TagManager.asset new file mode 100644 index 0000000000000000000000000000000000000000..2c3af945792a3c72fe52ddee61a8ab85d0399611 GIT binary patch literal 4308 zcmeHKyG|QH6uoP&4GHf@c!&nk&>$oifRLid1tGCSK)eJU8o_#nvB$7p7w+~uD4?U@ zGg9#Zq)CzgNR!Puvv|dVYZ~UR?#|pZckZ4!v&|LB{S_H~6p=BE7(`ZoGlKhL~ z*l_@C6OH5B``XWi-+JkK=Vr0``C)4IX(`8L*qTU$EwKqA5eLt&yhH;4h_jR!Jm-`! zqZ2p#4W5@jz~->WMQR>Sb1?f3toD@8wWN-lsZ(HXnfZlGog#7L1=+<;)?wV2;t5Zl zo_zW-_Tu>=#j`v=5{JFy$#eJOImq1dYo0^Ijc0idr+C718Zmi}VC==y++vjQd9Eie z*cf>(V%F7joM^;+*XX>Cko6~0ypYj(5_rPDn9=zYapT2|&QmGw${p_U3t|_}bI|zR zEim|I<{~o$%aI%?|A*4b_W}u(-;%oOctJ}VC;QDHJlFnK5M-btRt-_ZO%rCXOO@N~F&jF>%W#n;Wio8MENQ`HIqz}RyisSqui2>bZM3SMty-5$QnyZhPy D3J0i~ literal 0 HcmV?d00001 diff --git a/ProjectSettings/TimeManager.asset b/ProjectSettings/TimeManager.asset new file mode 100644 index 0000000000000000000000000000000000000000..0838e4ef1c2c998181d97129fe863478076acdef GIT binary patch literal 4116 zcmZQzV62p*h(nLs)uGdI;YF)uMawTQtjvm!M` z0VGmfl3Kvvn^=*VTbirjn3I!Vjx3QI4^k7HoS2geR}_#~RFavTlZwy|ay`TkUy_(XMUTvnW9nt|@r| zBnqf#=;$br=->?y??8!!IseSsv5B7md8Cs$=kxgY%s*adh=wMJcC8Z8Ztl4wy55{@ zwia9TCrJ{+U@(b4N%FyTS}Sk9v|fJu`u=w^{Q4@>alVABXg7#rz9msb3~tc7I>jFi ziW>|uPKQBvdF&JBsB0W(7!ST*J{uYdHZ`9ukuA->FE#roo&uy+TpY0s!y6$s| zz;kzi$Iz;&`G^7EnelpIy(7%^d>V!IVi)y%8in=p1`l|x;Qt=zbHj7tf1q-ry@2hQ zxk0ZuMPRS?aisIi?<4jNE!9c)@c>{O+@QCdBCy_r+?VMb0_T?Md{Q~lVMW(@s(%E$ zt$F@E83m8Qi@qIYT;|&`a1}-0j%Qr&OZuLiU{0Sm>W?ul(?1EWqNsl=;|6`_907k$ zb6@7q8E|g@`g2y}jHmvb18?i`_&xsQvcYkl0}VRx?HA{G?ghXYyy)9S#%1fe1g@gE zuFDzM`uH$Ff1LX=ef|)Y>Nmkv6!ozWnj1922?F&M4y|8&&-pu{d|2>Yxi;;4p0MS- zkka**BI-STEFyW&cf=s=JQto^2)l!l$1l1|>-8*)sL|7*6)is!fgeIBrxcHT73FVMe9E)BYtpr&oF<;?T3z>rc;L-TE0p~}i%dEB zVI9wU))mfN*OH5V*u&~({pgO>6SEHIQ=Sw0t~03TM2JC_XN!%?(Eh?W-SCg1YLx?( z1C;}n1C;}n1C;}n1OJx;H+ebBQU=j2(+v1o&tPS4kzrtSAkD{xJH Y(X??47LhuD!wwuA)5bZ@{b`B*FEb=R&j0`b literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2ae240b048b3eae10fea034ab688fc2737a9a4a6 GIT binary patch literal 23552 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