diff --git a/Assets/Plugins/UnityJSON/Attributes.cs b/Assets/Plugins/UnityJSON/Attributes.cs index cefc2bc..d355df8 100644 --- a/Assets/Plugins/UnityJSON/Attributes.cs +++ b/Assets/Plugins/UnityJSON/Attributes.cs @@ -172,7 +172,10 @@ public NodeOptions options { /// 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)] + [AttributeUsage ( + AttributeTargets.Field | + AttributeTargets.Property | + AttributeTargets.Parameter)] public class RestrictTypeAttribute : Attribute { private ObjectTypes _types; @@ -183,11 +186,31 @@ public RestrictTypeAttribute (ObjectTypes types) : base () _types = types; } - public RestrictTypeAttribute (ObjectTypes types, Type[] customTypes) : base () + public RestrictTypeAttribute (ObjectTypes types, params Type[] customTypes) : base () { if (customTypes == null) { throw new ArgumentNullException ("customTypes"); } + _types = types; + + if (!_types.SupportsCustom ()) { + throw new ArgumentException ("Attribute does not support custom types."); + } + + List typeList = new List (); + HashSet typeSet = new HashSet (); + foreach (Type type in customTypes) { + if (type != null + && !typeSet.Contains (type) + && Util.IsCustomType (type)) { + typeSet.Add (type); + typeList.Add (type); + } + } + + if (typeList.Count != 0) { + _customTypes = typeList.ToArray (); + } } /// @@ -205,26 +228,6 @@ public ObjectTypes types { /// 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 (); - } - } } } @@ -293,7 +296,7 @@ public object value { /// contain any data for the class / struct in order to /// prevent any unknown key errors. /// - public bool removeKey { get; set; } + public bool ignoreConditionKey { get; set; } } /// diff --git a/Assets/Plugins/UnityJSON/Deserializer.cs b/Assets/Plugins/UnityJSON/Deserializer.cs index 9ddb1b5..0e747a8 100644 --- a/Assets/Plugins/UnityJSON/Deserializer.cs +++ b/Assets/Plugins/UnityJSON/Deserializer.cs @@ -28,7 +28,6 @@ public DeserializationException (string message) : base (message) 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. @@ -41,35 +40,29 @@ public static Deserializer Default { if (value == null) { throw new ArgumentNullException ("default deserializer"); } - _simple = value; + _default = value; } } /// /// The initial deserializer that is provided by the framework. /// - public static Deserializer Simple { - get { return _simple; } - } + public static readonly Deserializer Simple = new Deserializer (); + + private Instantiater _instantiater = Instantiater.Default; /// - /// 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. + /// The instantiater associated with this deserializer. If not set, + /// the simple instantiater is used. /// - protected virtual bool TryInstantiate ( - JSONNode node, - Type type, - NodeOptions options, - out object instantiatedObject) - { - instantiatedObject = null; - return false; + public Instantiater instantiater { + get { return _instantiater; } + set { + if (value == null) { + throw new ArgumentNullException ("instantiater"); + } + _instantiater = value; + } } /// @@ -81,26 +74,101 @@ protected virtual bool TryInstantiate ( protected virtual bool TryDeserializeOn ( object obj, JSONNode node, - NodeOptions options) + NodeOptions options, + HashSet ignoredKeys) { return false; } /// /// Deserializes the JSON string directly on the object. Throws an - /// ArgumentNullException of the object is null. + /// ArgumentNullException of the object is null. This will first + /// try #TryDeserialize method and then IDeserializable.Deserialize if the + /// object implements the interface. This performs a general deserialization + /// and classes should prefer specific methods for manual deserialization. + /// + public void DeserializeOn ( + object obj, + string jsonString, + NodeOptions options = NodeOptions.Default, + HashSet ignoredKeys = null) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + DeserializeOn (obj, node, options, ignoredKeys); + } + + /// + /// Deserializes the JSON node directly on the object. Throws an + /// ArgumentNullException of the object is null. This will first + /// try #TryDeserialize method and then IDeserializable.Deserialize if the + /// object implements the interface. This performs a general deserialization + /// and classes should prefer specific methods for manual deserialization. /// public void DeserializeOn ( object obj, JSONNode node, - NodeOptions options = NodeOptions.Default) + NodeOptions options = NodeOptions.Default, + HashSet ignoredKeys = null) { if (obj == null) { throw new ArgumentNullException ("obj"); } + if (node == null) { + throw new ArgumentNullException ("node"); + } - 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."); + } + + if (ignoredKeys == null) { + ignoredKeys = new HashSet (); + } + _DeserializeOn (obj, node, options, ignoredKeys); + } + + /// + /// Deserializes the JSON string directly on the object. Throws an + /// ArgumentNullException of the object is null. This ignore + /// manual deserialization options (see Deserializer.TryDeserialize and + /// IDeserializable.Deserialize). + /// + public void DeserializeByParts ( + object obj, + string jsonString, + NodeOptions options = NodeOptions.Default, + HashSet ignoredKeys = null) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + DeserializeByParts (obj, node, options, ignoredKeys); + } + + /// + /// Deserializes the JSON node directly on the object. Throws an + /// ArgumentNullException of the object is null. This ignore + /// manual deserialization options (see Deserializer.TryDeserialize and + /// IDeserializable.Deserialize). + /// + public void DeserializeByParts ( + object obj, + JSONNode node, + NodeOptions options = NodeOptions.Default, + HashSet ignoredKeys = null) + { + if (obj == null) { + throw new ArgumentNullException ("obj"); + } + if (node == null) { + throw new ArgumentNullException ("node"); } Type type = obj.GetType (); @@ -108,20 +176,40 @@ public void DeserializeOn ( 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); - } + } + + if (ignoredKeys == null) { + ignoredKeys = new HashSet (); + } + _DeserializeByParts (obj, node, options, ignoredKeys); + } - _FeedCustom (obj, node, options); + /// + /// Deserializes the JSON string to a new object of the requested type. This + /// will first insantiate an object of the target type. The instantiation + /// will use the associated Instantiater for custom types. If an object can be + /// instantiated, #DeserializeOn method is used to feed the JSON into the object. + /// + /// JSON string to deserialize. + /// Requested type of the deserialized object. + /// Deserialization options for the node (optional). + public object Deserialize ( + string jsonString, + Type type, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return Deserialize (node, type, 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. + /// will first insantiate an object of the target type. The instantiation + /// will use the associated Instantiater for custom types. If an object can be + /// instantiated, #DeserializeOn method is used to feed the JSON into the object. /// /// JSON node to deserialize. /// Requested type of the deserialized object. @@ -131,7 +219,34 @@ public object Deserialize ( Type type, NodeOptions options = NodeOptions.Default) { - return _Deserialize (node, type, options, ObjectTypes.JSON, null); + if (node == null) { + throw new ArgumentNullException ("node"); + } + return Deserialize (node, type, options, ObjectTypes.JSON, null); + } + + /// + /// Deserializes a JSON string 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 string 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 ( + string jsonString, + ObjectTypes restrictedTypes = ObjectTypes.JSON, + Type[] customTypes = null, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToObject (jsonString, restrictedTypes, customTypes, options); } /// @@ -151,8 +266,8 @@ public object DeserializeToObject ( Type[] customTypes = null, NodeOptions options = NodeOptions.Default) { - if (node == null || node.IsNull) { - return null; + if (node == null) { + throw new ArgumentNullException ("node"); } if (customTypes != null) { if (!restrictedTypes.SupportsCustom ()) { @@ -167,6 +282,20 @@ public object DeserializeToObject ( return _DeserializeToObject (node, options, restrictedTypes, customTypes); } + /// + /// Deserializes a JSON string into a System.Nullable object. + /// + public Nullable DeserializeToNullable ( + string jsonString, + NodeOptions options = NodeOptions.Default) where T : struct + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToNullable (node, options); + } + /// /// Deserializes a JSON node into a System.Nullable object. /// @@ -174,12 +303,31 @@ public Nullable DeserializeToNullable ( JSONNode node, NodeOptions options = NodeOptions.Default) where T : struct { - if (node == null || node.IsNull) { + if (node == null) { + throw new ArgumentNullException ("node"); + } + if (node.IsNull || node.Tag == JSONNodeType.None) { return null; } return (Nullable)Deserialize (node, typeof(T), options); } + /// + /// Deserializes a JSON string into an integer. Throws an ArgumentNullException + /// if the string is null. Throws a CastException if the node does + /// not contain an integer. + /// + public int DeserializeToInt ( + string jsonString, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToInt (node, options); + } + /// /// Deserializes a JSON node into an integer. Throws an ArgumentNullException /// if the node is null. Throws a CastException if the node does @@ -195,6 +343,22 @@ public int DeserializeToInt ( return (int)_DeserializeToInt (node, options); } + /// + /// Deserializes a JSON string into an unsigned integer. Throws an ArgumentNullException + /// if the string is null. Throws a CastException if the node does + /// not contain an unsigned integer. + /// + public uint DeserializeToUInt ( + string jsonString, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToUInt (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 @@ -210,6 +374,22 @@ public uint DeserializeToUInt ( return (uint)_DeserializeToUInt (node, options); } + /// + /// Deserializes a JSON string into a byte. Throws an ArgumentNullException + /// if the string is null. Throws a CastException if the node does + /// not contain a byte. + /// + public byte DeserializeToByte ( + string jsonString, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToByte (node, options); + } + /// /// Deserializes a JSON node into a byte. Throws an ArgumentNullException /// if the node is null. Throws a CastException if the node does @@ -225,6 +405,22 @@ public byte DeserializeToByte ( return (byte)_DeserializeToByte (node, options); } + /// + /// Deserializes a JSON string into a boolean. Throws an ArgumentNullException + /// if the string is null. Throws a CastException if the node does + /// not contain a boolean. + /// + public bool DeserializeToBool ( + string jsonString, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToBool (node, options); + } + /// /// Deserializes a JSON node into a boolean. Throws an ArgumentNullException /// if the node is null. Throws a CastException if the node does @@ -240,6 +436,22 @@ public bool DeserializeToBool ( return (bool)_DeserializeToBool (node, options); } + /// + /// Deserializes a JSON string into a float. Throws an ArgumentNullException + /// if the string is null. Throws a CastException if the node does + /// not contain a float. + /// + public float DeserializeToFloat ( + string jsonString, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToFloat (node, options); + } + /// /// Deserializes a JSON node into a float. Throws an ArgumentNullException /// if the node is null. Throws a CastException if the node does @@ -255,6 +467,22 @@ public float DeserializeToFloat ( return (float)_DeserializeToFloat (node, options); } + /// + /// Deserializes a JSON string into a double. Throws an ArgumentNullException + /// if the string is null. Throws a CastException if the node does + /// not contain a double. + /// + public double DeserializeToDouble ( + string jsonString, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToDouble (node, options); + } + /// /// Deserializes a JSON node into a double. Throws an ArgumentNullException /// if the node is null. Throws a CastException if the node does @@ -270,6 +498,22 @@ public double DeserializeToDouble ( return (double)_DeserializeToDouble (node, options); } + /// + /// Deserializes a JSON string into a long. Throws an ArgumentNullException + /// if the string is null. Throws a CastException if the node does + /// not contain a long. + /// + public long DeserializeToLong ( + string jsonString, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToLong (node, options); + } + /// /// Deserializes a JSON node into a long. Throws an ArgumentNullException /// if the node is null. Throws a CastException if the node does @@ -285,6 +529,21 @@ public long DeserializeToLong ( return (long)_DeserializeToLong (node, options); } + /// + /// Deserializes a JSON string into a string. Throws an ArgumentNullException + /// if the node is null. + /// + public string DeserializeToString ( + string jsonString, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToString (node, options); + } + /// /// Deserializes a JSON node into a string. /// @@ -292,12 +551,31 @@ public string DeserializeToString ( JSONNode node, NodeOptions options = NodeOptions.Default) { - if (node == null || node.IsNull) { + if (node == null) { + throw new ArgumentNullException ("node"); + } + if (node.IsNull || node.Tag == JSONNodeType.None) { return null; } return _DeserializeToString (node, options); } + /// + /// Deserializes a JSON string into an enum. Throws an ArgumentNullException + /// if the string is null. Throws an ArgumentException if the generic + /// type T is not an enum. + /// + public T DeserializeToEnum ( + string jsonString, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToEnum (node, options); + } + /// /// Deserializes a JSON node into an enum. Throws an ArgumentNullException /// if the node is null. Throws an ArgumentException if the generic @@ -307,7 +585,7 @@ public T DeserializeToEnum ( JSONNode node, NodeOptions options = NodeOptions.Default) { - if (node == null || node.IsNull) { + if (node == null) { throw new ArgumentNullException ("node"); } if (!typeof(T).IsEnum) { @@ -316,6 +594,20 @@ public T DeserializeToEnum ( return (T)_DeserializeToEnum (node, typeof(T), options); } + /// + /// Deserializes a JSON string into a generic list. + /// + public List DeserializeToList ( + string jsonString, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToList (node, options); + } + /// /// Deserializes a JSON node into a generic list. /// @@ -323,7 +615,10 @@ public List DeserializeToList ( JSONNode node, NodeOptions options = NodeOptions.Default) { - if (node == null || node.IsNull) { + if (node == null) { + throw new ArgumentNullException ("node"); + } + if (node.IsNull || node.Tag == JSONNodeType.None) { return null; } var list = new List (); @@ -331,6 +626,30 @@ public List DeserializeToList ( return list; } + /// + /// Deserializes a JSON string 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 string 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 ( + string jsonString, + ObjectTypes restrictedTypes = ObjectTypes.JSON, + Type[] customTypes = null, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToObjectList (node, restrictedTypes, customTypes, options); + } + /// /// Deserializes a JSON node into a System.Object list. If no restrictions /// are given, the deserialized types can be doubles, booleans, strings, and arrays @@ -348,7 +667,10 @@ public List DeserializeToObjectList ( Type[] customTypes = null, NodeOptions options = NodeOptions.Default) { - if (node == null || node.IsNull) { + if (node == null) { + throw new ArgumentNullException ("node"); + } + if (node.IsNull || node.Tag == JSONNodeType.None) { return null; } var list = new List (); @@ -356,6 +678,20 @@ public List DeserializeToObjectList ( return list; } + /// + /// Deserializes a JSON string into a generic dictionary. + /// + public Dictionary DeserializeToDictionary ( + string jsonString, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToDictionary (node, options); + } + /// /// Deserializes a JSON node into a generic dictionary. /// @@ -363,7 +699,10 @@ public Dictionary DeserializeToDictionary ( JSONNode node, NodeOptions options = NodeOptions.Default) { - if (node == null || node.IsNull) { + if (node == null) { + throw new ArgumentNullException ("node"); + } + if (node.IsNull || node.Tag == JSONNodeType.None) { return null; } var dictionary = new Dictionary (); @@ -371,6 +710,34 @@ public Dictionary DeserializeToDictionary ( return dictionary; } + /// + /// Deserializes a JSON string 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 string 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 ( + string jsonString, + ObjectTypes restrictedTypes = ObjectTypes.JSON, + Type[] customTypes = null, + NodeOptions options = NodeOptions.Default) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return DeserializeToObjectDictionary ( + node, + restrictedTypes, + customTypes, + options); + } + /// /// 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, @@ -388,7 +755,10 @@ public Dictionary DeserializeToObjectDictionary ( Type[] customTypes = null, NodeOptions options = NodeOptions.Default) { - if (node == null || node.IsNull) { + if (node == null) { + throw new ArgumentNullException ("node"); + } + if (node.IsNull || node.Tag == JSONNodeType.None) { return null; } var dictionary = new Dictionary (); @@ -403,45 +773,39 @@ public Dictionary DeserializeToObjectDictionary ( return dictionary; } - private object _Deserialize ( + internal object Deserialize ( JSONNode node, - Type type, + Type targetType, NodeOptions options, - ObjectTypes types, + ObjectTypes restrictedTypes, Type[] customTypes) { - if (node == null || node.IsNull) { + if (node.IsNull || node.Tag == JSONNodeType.None) { 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 (targetType == typeof(object)) { + return _DeserializeToObject (node, options, restrictedTypes, customTypes); } - if (type.IsValueType) { - if (type.IsEnum) { - return _DeserializeToEnum (node, type, options); - } else if (type.IsPrimitive) { - return _DeserializeToPrimitive (node, type, options); + if (targetType.IsValueType) { + if (targetType.IsEnum) { + return _DeserializeToEnum (node, targetType, options); + } else if (targetType.IsPrimitive) { + return _DeserializeToPrimitive (node, targetType, options); } } else { - if (type == typeof(string)) { + if (targetType == 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); + } else if (Nullable.GetUnderlyingType (targetType) != null) { + return _DeserializeToNullable (node, targetType, options); + } else if (typeof(IList).IsAssignableFrom (targetType)) { + return _DeserializeToIList (node, targetType, options, restrictedTypes, customTypes); + } else if (Util.IsDictionary (targetType)) { + return _DeserializeToIDictionary (node, targetType, options, restrictedTypes, customTypes); } } - return _DeserializeCustom (node, type, options); + return _DeserializeToCustom (node, targetType, options); } private object _Deserialize ( @@ -454,7 +818,7 @@ private object _Deserialize ( ? 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); + return Deserialize (node, type, options, types, customTypes); } private object _DeserializeToObject ( @@ -512,7 +876,7 @@ private object _DeserializeToObject ( } return _DeserializeToString (node, options); } else { - return _HandleUnknown (options, "Unknown JSON node type " + node); + return null; } } @@ -529,7 +893,7 @@ private object _DeserializeToPrimitive (JSONNode node, Type type, NodeOptions op } else if (type == typeof(byte)) { return _DeserializeToByte (node, options); } else if (type == typeof(long)) { - return _DeserializeToByte (node, options); + return _DeserializeToLong (node, options); } else if (type == typeof(uint)) { return _DeserializeToUInt (node, options); } else if (type == typeof(bool)) { @@ -774,7 +1138,7 @@ private void _FeedList ( while (enumerator.MoveNext ()) { JSONNode child = (JSONNode)enumerator.Current; // Throws an error if needed. - list.Add (_Deserialize ( + list.Add (Deserialize ( child, genericArgument, options & ~NodeOptions.ReplaceDeserialized, @@ -816,7 +1180,7 @@ private void _FeedDictionary ( while (enumerator.MoveNext ()) { var pair = (KeyValuePair)enumerator.Current; // Use default field options to throw at any error. - object key = _Deserialize ( + object key = Deserialize ( new JSONString (pair.Key), keyType, NodeOptions.Default, @@ -824,7 +1188,7 @@ private void _FeedDictionary ( null /* customTypes */); // Throws an error if needed. - object value = _Deserialize ( + object value = Deserialize ( pair.Value, valueType, options & ~NodeOptions.ReplaceDeserialized, @@ -837,108 +1201,63 @@ private void _FeedDictionary ( } } - private object _DeserializeCustom (JSONNode node, Type type, NodeOptions options) + private object _DeserializeToCustom (JSONNode node, Type targetType, 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 ())) { - JSONNode value = node [condition.key]; - if (condition.removeKey) { - node.Remove (condition.key); - } - object result = Deserialize (node, condition.referenceType, options); - if (condition.removeKey) { - node.Add (condition.key, value); - } - return result; - } - } - - var defaultAttribute = Util.GetAttribute (type); - if (defaultAttribute != null) { - return Deserialize (node, defaultAttribute.referenceType, options); - } - - object obj = null; - KeyValuePair[] removedKeys = null; - ConstructorInfo[] constructors = type.GetConstructors ( - BindingFlags.Instance | - BindingFlags.Public | - BindingFlags.NonPublic); - foreach (ConstructorInfo constructor in constructors) { - var constructorAttribute = Util.GetAttribute (constructor); - if (constructorAttribute != null) { - obj = _CreateObjectWithConstructor (type, constructor, node, out removedKeys); - break; - } - } - - if (obj == null) { - try { - obj = Activator.CreateInstance (type); - } catch (Exception) { - return _HandleUnknown (options, "Unknown type " + type + " cannot be instantiated."); - } - } + InstantiationData instantiationData = instantiater.Instantiate ( + node, + targetType, + null /* referingType */, + options, + this); - DeserializeOn (obj, node, options); - if (removedKeys != null) { - foreach (var pair in removedKeys) { - node.Add (pair.Key, pair.Value); - } + if (!instantiationData.needsDeserialization) { + return instantiationData.instantiatedObject; } - return obj; + _DeserializeOn ( + instantiationData.instantiatedObject, + node, + options, + instantiationData.ignoredKeys); + return instantiationData.instantiatedObject; } - private object _CreateObjectWithConstructor ( - Type type, - ConstructorInfo constructor, + private void _DeserializeOn ( + object obj, JSONNode node, - out KeyValuePair[] removedKeys) + NodeOptions options, + HashSet ignoredKeys) { - ParameterInfo[] parameters = constructor.GetParameters (); - object[] parameterValues = new object[parameters.Length]; - removedKeys = new KeyValuePair[parameterValues.Length]; - - for (int i = 0; i < parameterValues.Length; i++) { - var parameterAttribute = Util.GetAttribute (parameters [i]); - string key = parameterAttribute != null && parameterAttribute.key != null - ? parameterAttribute.key : parameters [i].Name; - parameterValues [i] = Deserialize ( - node [key], - parameters [i].ParameterType, - parameterAttribute == null ? NodeOptions.Default : parameterAttribute.options); - - removedKeys [i] = new KeyValuePair (key, node [key]); - node.Remove (key); + if (TryDeserializeOn (obj, node, options, ignoredKeys)) { + return; } - return Activator.CreateInstance (type, parameterValues); - } - - private void _FeedCustom (object filledObject, JSONNode node, NodeOptions options) - { - if (filledObject is IDeserializable) { - (filledObject as IDeserializable).Deserialize (node, this); + if (obj is IDeserializable) { + (obj as IDeserializable).Deserialize (node, this); return; } + _DeserializeByParts (obj, node, options, ignoredKeys); + } - var listener = filledObject as IDeserializationListener; + private void _DeserializeByParts ( + object obj, + JSONNode node, + NodeOptions options, + HashSet ignoredKeys) + { + var listener = obj as IDeserializationListener; if (listener != null) { listener.OnDeserializationWillBegin (this); } if (node.IsObject) { try { - Type type = filledObject.GetType (); + Type type = obj.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 (); + IEnumerator enumerator = (node as JSONObject).GetEnumerator (); var extrasTypeAttribute = extrasMember == null ? null : Util.GetAttribute (extrasMember); @@ -949,8 +1268,12 @@ private void _FeedCustom (object filledObject, JSONNode node, NodeOptions option while (enumerator.MoveNext ()) { var pair = (KeyValuePair)enumerator.Current; + if (ignoredKeys.Contains (pair.Key)) { + continue; + } + if (members.ContainsKey (pair.Key)) { - _DeserializeClassMember (filledObject, members [pair.Key], pair.Value); + _DeserializeClassMember (obj, members [pair.Key], pair.Value); } else { if (extrasMember != null) { extras.Add (pair.Key, _DeserializeToObject ( @@ -971,7 +1294,7 @@ private void _FeedCustom (object filledObject, JSONNode node, NodeOptions option if (extrasMember != null) { if (extras.Count != 0 || extrasAttribute.options.ShouldAssignNull ()) { - Util.SetMemberValue (extrasMember, filledObject, extras); + Util.SetMemberValue (extrasMember, obj, extras); } } @@ -984,6 +1307,10 @@ private void _FeedCustom (object filledObject, JSONNode node, NodeOptions option } throw exception; } + } else if (node.IsNull || node.Tag == JSONNodeType.None) { + if (listener != null) { + listener.OnDeserializationSucceeded (this); + } } else { if (listener != null) { listener.OnDeserializationFailed (this); @@ -1010,7 +1337,7 @@ private void _DeserializeClassMember ( && !options.ShouldReplaceWithDeserialized ()) { var value = Util.GetMemberValue (memberInfo, filledObject); if (value != null) { - DeserializeOn (value, node, options); + _DeserializeOn (value, node, options, new HashSet ()); return; } } diff --git a/Assets/Plugins/UnityJSON/Enums.cs b/Assets/Plugins/UnityJSON/Enums.cs index 650bfef..0b16345 100644 --- a/Assets/Plugins/UnityJSON/Enums.cs +++ b/Assets/Plugins/UnityJSON/Enums.cs @@ -44,19 +44,19 @@ public enum NodeOptions IgnoreTypeMismatch = 1 << 3, /// - /// If the C# type cannot be instantiated, then an error is thrown. + /// If the C# type cannot be instantiated, then an InstantiationException 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, + IgnoreInstantiationError = 1 << 4, /// - /// Ignore both the type mismatch and the unknown type errors for + /// Ignore both the type mismatch and instantiation errors for /// deserialization. /// - IgnoreDeserializationTypeErrors = IgnoreTypeMismatch | IgnoreUnknownType, + IgnoreDeserializationTypeErrors = IgnoreTypeMismatch | IgnoreInstantiationError, /// /// When deserializing a JSON string, the null or undefined values are @@ -241,7 +241,7 @@ public static bool ShouldIgnoreTypeMismatch (this NodeOptions options) /// public static bool ShouldIgnoreUnknownType (this NodeOptions options) { - return (options & NodeOptions.IgnoreUnknownType) != 0; + return (options & NodeOptions.IgnoreInstantiationError) != 0; } /// diff --git a/Assets/Plugins/UnityJSON/Instantiater.cs b/Assets/Plugins/UnityJSON/Instantiater.cs new file mode 100644 index 0000000..6f8ec68 --- /dev/null +++ b/Assets/Plugins/UnityJSON/Instantiater.cs @@ -0,0 +1,365 @@ +using SimpleJSON; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace UnityJSON +{ + /// + /// An exception during the instantiation process. + /// + public class InstantiationException : Exception + { + public InstantiationException () : base () + { + } + + public InstantiationException (string message) : base (message) + { + } + } + + /// + /// The data associated with an instantiated object. Other than + /// the object itself, holds reference to additional information + /// regarding the upcoming deserialization process. + /// + public struct InstantiationData + { + /// + /// An instantiated data representing a null object. + /// + public static readonly InstantiationData Null = new InstantiationData (); + + /// + /// The instantiated object that will be deserialized. + /// + public object instantiatedObject { + get { return _instantiatedObject; } + set { _instantiatedObject = value; } + } + + /// + /// Whether deserialization is necessary. If the instantiated + /// object is null this value always returns false. + /// + public bool needsDeserialization { + get { return _instantiatedObject != null && _needsDeserialization; } + set { _needsDeserialization = value; } + } + + /// + /// The keys that should be ignored during the deserialization. + /// These keys for instance can be for instance used for instantiation + /// with a constructor. + /// + public HashSet ignoredKeys { + get { + if (_ignoredKeys == null) { + _ignoredKeys = new HashSet (); + } + return _ignoredKeys; + } + set { _ignoredKeys = value; } + } + + private object _instantiatedObject; + private bool _needsDeserialization; + private HashSet _ignoredKeys; + } + + /// + /// Instantiates an instance of a type. + /// + public class Instantiater + { + public static readonly Instantiater Default = new Instantiater (); + + /// + /// Instantiates an instance of a type. First, TryInstantiate method is + /// called for custom instantiation. If that fails, then the class is queried for + /// conditional and default substitute types. If no such types exist or + /// match, then an object of the given type is created directly. If the + /// class or the struct has a constructor with the attribute + /// JSONConstructorAttribute, then that constructor is used, otherwise + /// the default constructor is used. + /// + /// If the instantiated object is null, then InstantiationData.Null is + /// returned. + /// + /// JSON node to create the instance from. This + /// might be used to decide the type of the object or for the arguments + /// of a constructor. + /// Target type of the object. + /// Refering type that is only set if this + /// is a substitute type for another type. + /// Instantiation options. + /// Deserializer to use for the instantiation. + public InstantiationData Instantiate ( + JSONNode node, + Type targetType, + Type referingType = null, + NodeOptions options = NodeOptions.Default, + Deserializer deserializer = null) + { + if (node == null) { + throw new ArgumentNullException ("node"); + } + if (targetType == null) { + throw new ArgumentNullException ("targetType"); + } + if (deserializer == null) { + deserializer = Deserializer.Default; + } + return _Instantiate (node, targetType, referingType, options, deserializer); + } + + /// + /// Instantiates an instance of a type. First, TryInstantiate method is + /// called for custom instantiation. If that fails, then the class is queried for + /// conditional and default substitute types. If no such types exist or + /// match, then an object of the given type is created directly. If the + /// class or the struct has a constructor with the attribute + /// JSONConstructorAttribute, then that constructor is used, otherwise + /// the default constructor is used. + /// + /// If the instantiated object is null, then InstantiationData.Null is + /// returned. + /// + /// JSON string to create the instance from. This + /// might be used to decide the type of the object or for the arguments + /// of a constructor. + /// Target type of the object. + /// Refering type that is only set if this + /// is a substitute type for another type. + /// Instantiation options. + /// Deserializer to use for the instantiation. + public InstantiationData Instantiate ( + string jsonString, + Type targetType, + Type referingType = null, + NodeOptions options = NodeOptions.Default, + Deserializer deserializer = null) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return Instantiate (node, targetType, referingType, options, deserializer); + } + + /// + /// Instantiates an object with a suitable constructor. If the + /// class or the struct has a constructor with the attribute + /// JSONConstructorAttribute, then that constructor is used, otherwise + /// the default constructor is used. + /// + /// If the instantiated object is null, then InstantiationData.Null is + /// returned. + /// + /// JSON string to create the instance from. This + /// might be used for the arguments of the constructor. + /// Target type. + /// Options. + /// Deserializer. + public InstantiationData InstantiateWithConstructor ( + string jsonString, + Type targetType, + NodeOptions options = NodeOptions.Default, + Deserializer deserializer = null) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + JSONNode node = SimpleJSON.JSON.Parse (jsonString); + return InstantiateWithConstructor (node, targetType, options, deserializer); + } + + /// + /// Instantiates an object with a suitable constructor. If the + /// class or the struct has a constructor with the attribute + /// JSONConstructorAttribute, then that constructor is used, otherwise + /// the default constructor is used. + /// + /// If the instantiated object is null, then InstantiationData.Null is + /// returned. + /// + /// JSON node to create the instance from. This + /// might be used for the arguments of the constructor. + /// Target type. + /// Options. + /// Deserializer. + public InstantiationData InstantiateWithConstructor ( + JSONNode node, + Type targetType, + NodeOptions options = NodeOptions.Default, + Deserializer deserializer = null) + { + if (node == null) { + throw new ArgumentNullException ("node"); + } + if (targetType == null) { + throw new ArgumentNullException ("targetType"); + } + if (deserializer == null) { + deserializer = Deserializer.Default; + } + + return _InstantiateWithConstructor (node, targetType, options, deserializer); + } + + /// + /// Tries to instantiate an object of a specific type. This performs + /// application specific instantiation and subclasses should override + /// this method to perform their own logic. If the instantiated object is + /// null, then InstantiationData.Null should be returned. + /// + /// JSON node to create the instance from. This + /// might be used to decide the type of the object or for the arguments + /// of a constructor. + /// Target type of the object. + /// Refering type that is only set if this + /// is a substitute type for another type. + /// Instantiation options. + /// Deserializer to use for the instantiation. + protected virtual bool TryInstantiate ( + JSONNode node, + Type targetType, + Type referingType, + NodeOptions options, + Deserializer deserializer, + out InstantiationData instantiationData) + { + instantiationData = InstantiationData.Null; + return false; + } + + private InstantiationData _Instantiate ( + JSONNode node, + Type targetType, + Type referingType, + NodeOptions options, + Deserializer deserializer) + { + InstantiationData instantiationData; + if (TryInstantiate ( + node, + targetType, + referingType, + options, + deserializer, + out instantiationData)) { + return instantiationData; + } + + if (referingType != targetType) { + var conditionalAttributes = targetType + .GetCustomAttributes (typeof(ConditionalInstantiationAttribute), false); + foreach (object attribute in conditionalAttributes) { + var condition = attribute as ConditionalInstantiationAttribute; + if (Equals (node [condition.key].Value, condition.value.ToString ())) { + instantiationData = _Instantiate ( + node, + condition.referenceType, + targetType, + options, + deserializer); + if (condition.ignoreConditionKey) { + instantiationData.ignoredKeys = new HashSet () { condition.key }; + } + return instantiationData; + } + } + + var defaultAttribute = Util.GetAttribute (targetType); + if (defaultAttribute != null) { + return _Instantiate ( + node, + defaultAttribute.referenceType, + targetType, + options, + deserializer); + } + } + + return _InstantiateWithConstructor ( + node, + targetType, + options, + deserializer); + } + + private InstantiationData _InstantiateWithConstructor ( + JSONNode node, + Type targetType, + NodeOptions options, + Deserializer deserializer) + { + ConstructorInfo[] constructors = targetType.GetConstructors ( + BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.NonPublic); + foreach (ConstructorInfo constructorInfo in constructors) { + var constructorAttribute = Util.GetAttribute (constructorInfo); + if (constructorAttribute != null) { + return _InstantiateWithConstructor (node, constructorInfo, deserializer); + } + } + + try { + InstantiationData instantiationData = new InstantiationData(); + instantiationData.instantiatedObject = Activator.CreateInstance (targetType); + instantiationData.needsDeserialization = node.Count != 0; + return instantiationData; + } catch (Exception) { + return _HandleError (options, "Type " + targetType + + " does not have a suitable constructor."); + } + } + + private InstantiationData _InstantiateWithConstructor ( + JSONNode node, + ConstructorInfo constructorInfo, + Deserializer deserializer) + { + ParameterInfo[] parameters = constructorInfo.GetParameters (); + object[] parameterValues = new object[parameters.Length]; + HashSet ignoredKeys = new HashSet (); + + for (int i = 0; i < parameterValues.Length; i++) { + var nodeAttribute = Util.GetAttribute (parameters [i]); + var restrictAttribute = Util.GetAttribute (parameters [i]); + + string key = nodeAttribute != null && nodeAttribute.key != null + ? nodeAttribute.key : parameters [i].Name; + ObjectTypes restrictedTypes = restrictAttribute == null + ? ObjectTypes.JSON : restrictAttribute.types; + Type[] customTypes = restrictAttribute == null ? null : restrictAttribute.customTypes; + + parameterValues [i] = deserializer.Deserialize ( + node [key], + parameters [i].ParameterType, + nodeAttribute == null ? NodeOptions.Default : nodeAttribute.options, + restrictedTypes, + customTypes); + + ignoredKeys.Add (key); + } + + InstantiationData instantiationData = new InstantiationData(); + instantiationData.instantiatedObject = constructorInfo.Invoke (parameterValues); + instantiationData.needsDeserialization = ignoredKeys.Count != node.Count; + instantiationData.ignoredKeys = ignoredKeys; + return instantiationData; + } + + private InstantiationData _HandleError (NodeOptions options, string message) + { + if (options.ShouldIgnoreUnknownType ()) { + return InstantiationData.Null; + } else { + throw new InstantiationException (message); + } + } + } +} diff --git a/Assets/Plugins/UnityJSON/Instantiater.cs.meta b/Assets/Plugins/UnityJSON/Instantiater.cs.meta new file mode 100644 index 0000000..b88a26b --- /dev/null +++ b/Assets/Plugins/UnityJSON/Instantiater.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 29eae2c7407bd438398782e1260b5458 +timeCreated: 1506094023 +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 index 73b4303..fd4a286 100644 --- a/Assets/Plugins/UnityJSON/Serializer.cs +++ b/Assets/Plugins/UnityJSON/Serializer.cs @@ -18,8 +18,7 @@ public class Serializer private const string _kTrue = "true"; private const string _kFalse = "false"; - private static Serializer _default = new Serializer (); - private static Serializer _simple = _default; + private static Serializer _default = new Serializer(); /// /// The default serializer to be used when no serializer is given. @@ -32,16 +31,14 @@ public static Serializer Default { if (value == null) { throw new ArgumentNullException ("default serializer"); } - _simple = value; + _default = value; } } /// /// The initial serializer that is provided by the framework. /// - public static Serializer Simple { - get { return _simple; } - } + private static readonly Serializer Simple = new Serializer (); /// /// When set to true, the keyword undefined is used instead of null @@ -77,6 +74,9 @@ protected virtual bool TrySerialize ( /// then ISerializable.Serialize method if the object implements it. /// If these are not implemented, then the framework serialization is /// performed. + /// + /// This is the general serialization function and an object specific + /// function should be prefered when performing manual serialization. /// public string Serialize (object obj, NodeOptions options = NodeOptions.Default) { @@ -260,7 +260,7 @@ public string SerializeColor (Color color) /// /// Serializes Unity Rect into JSON string. /// - private string SerializeRect (Rect rect) + public string SerializeRect (Rect rect) { return "{\"x\":" + rect.x + ",\"y\":" + rect.y + ",\"width\":" + rect.width + ",\"height\":" + rect.height + "}"; @@ -269,12 +269,25 @@ private string SerializeRect (Rect rect) /// /// Serializes Unity Bounds into JSON string. /// - private string SerializeBounds (Bounds bounds) + public string SerializeBounds (Bounds bounds) { return "{\"center\":" + SerializeVector3 (bounds.center) + ",\"extents\":" + SerializeVector3 (bounds.extents) + "}"; } + /// + /// Serializes an object by its fields and properties. This will + /// ignore custom serializations of the object (see Serializer.TrySerialize + /// and ISerializable.Serialize). + /// + public string SerializeByParts (object obj, NodeOptions options = NodeOptions.Default) + { + if (obj == null) { + return SerializeNull (options); + } + return _SerializeCustom (obj, options); + } + private string _SerializeString (string stringValue) { return "\"" + stringValue.Replace ("\"", "\\\"") + "\""; diff --git a/README.md b/README.md index e1fe681..cbbf015 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,11 @@ - [Inheritance](#inheritance) - [Constructors](#constructors) - [Deserialization Lifecycle](#deserialization-lifecycle) + - [Custom Deserialization with Instantiater](#custom-deserialization-with-instantiater) - [Custom Deserialization with Deserializer](#custom-deserialization-with-deserializer) - [Custom Deserialization with IDeserializable](#custom-deserialization-with-ideserializable) * [Changelog](#changelog) + - [v2.0](#v20) - [v1.1](#v11) ## Features @@ -234,7 +236,7 @@ serialization lifecycle calls from `ISerializationListener`. ## Deserialization -You can perform deserialization by calling `JSON.Deserialize\(jsonString)` method. +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 @@ -289,7 +291,7 @@ public class AClass // 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)] + [JSONNode(NodeOptions.IgnoreInstantiationError)] public ClassWithConstructor classField; // Even if the JSON object has a "customField" key, don't @@ -306,7 +308,7 @@ case of any problems. By default, the exception is thrown in the following scena 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. +type error. You can ignore it with the `NodeOptions.IgnoreInstantiationError` 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 @@ -451,8 +453,8 @@ as the object. ### 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 +would be to use a custom instantiater +(see [Custom Deserialization with Instantiater](#custom-deserialization-with-instantiater)), 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 @@ -535,40 +537,62 @@ public class AClass : IDeserializationListener 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 +## Custom Deserialization with Instantiater -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. +Intantiater is the component that is responsible for instantiating instances of +an object. The basic instantiater can be accessed with `Instantiater.Simple`. Every +deserializer has an instantiater associated with it. You can subclass the +instantiater to perform your application-specific logic in +`Instantiater.TryInstantiate`. ```cs -public class SpecialDeserializer : Deserializer +public class SpecialInstantiater : Instantiater { protected override bool TryInstantiate ( JSONNode node, - Type type, + Type targetType, + Type referingType, NodeOptions options, - out object instantiatedObject) + Deserializer deserializer, + out InstantiationData instantiationData) { if (type == typeof(MySpecialClass)) { // Custom deserializers can be used to instantiate classes // with constructors. - instantiatedObject = new MySpecialClass(node["key"]); + instantiationData = new InstantiationData(); + instantiationData.instantiatedObject + = new MySpecialClass(node["key"]); + instantiationData.needsDeserialization = true; + instantiationData.ignoredKeys = new HashSet { "key "}; return true; } else { // Returning false will simply run the regular // instantiation process. + instantiationData = InstantiationData.Null; return false; } } +} + +Deserializer.Default.instantiater = new SpecialInstantiater(); +var obj = JSON.Deserialize(jsonString); +``` + +## 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. The +deserializer has an associated `Instantiater` to instantiate instances of objects. +You can change this instantiater from `Deserializer.instantiater`. You can +create your own deserializer by simply subclassing `Deserializer`. You should then +override the `Deserializer.TryDeserializeOn` method +to perform your application specific deserialization. + +```cs +public class SpecialDeserializer : Deserializer +{ protected override bool TryDeserialize ( object obj, JSONNode node, @@ -586,16 +610,15 @@ public class SpecialDeserializer : Deserializer } } -var deserializer = new SpecialDeserializer(); -var obj = JSON.Deserialize(jsonString, deserializer); +mySpecialObject.FeedJSON(jsonString, new SpecialDeserializer()); ``` 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 +instantiate the object with its assigned instantiater. 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. +method, then `IDeserializable.Deserialize` if the object implements the interface, +and finally performs the regular framework deserialization if all fails. The classes that are deserialized with the `Deserializer.TryDeserialize` method do not receive deserialization lifecycle calls from `IDeserializationListener`. @@ -622,6 +645,16 @@ not receive deserialization lifecycle calls from `IDeserializationListener`. ## Changelog +### v2.0 + +- Bug fixes +- Added Serializer.SerializeByParts +- Added Deserializer.DeserializeByParts and deserializer methods taking JSON +string arguments +- Created the class Instantiater +- Allows use of RestrictTypeAttribute with constructor arguments +- Introduces InstantiationData to work around ignored keys + ### v1.1 - Added `JSONConstructorAttribute` diff --git a/unityjson.unitypackage b/unityjson.unitypackage index 5cbc581..aecfe47 100644 Binary files a/unityjson.unitypackage and b/unityjson.unitypackage differ