diff --git a/Assets/Plugins/UnityJSON/Deserializer.cs b/Assets/Plugins/UnityJSON/Deserializer.cs index 0e747a8..142107b 100644 --- a/Assets/Plugins/UnityJSON/Deserializer.cs +++ b/Assets/Plugins/UnityJSON/Deserializer.cs @@ -65,6 +65,10 @@ public Instantiater instantiater { } } + protected Deserializer () + { + } + /// /// 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 @@ -1360,12 +1364,17 @@ private Dictionary> _GetDeserializedClassMembers ( out MemberInfo extrasMember, out JSONExtrasAttribute extrasAttribute) { - JSONObjectAttribute classAttribute = Util.GetAttribute (classType); + JSONObjectAttribute objectAttribute = Util.GetAttribute (classType); Dictionary> members = new Dictionary> (); var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - if (classAttribute != null && !classAttribute.options.ShouldIgnoreStatic ()) { - flags |= BindingFlags.Static; + if (objectAttribute != null) { + if (!objectAttribute.options.ShouldIgnoreStatic ()) { + flags |= BindingFlags.Static; + } + if (objectAttribute.options.ShouldUseTupleFormat ()) { + throw new ArgumentException ("Cannot deserialize on a tuple formatted object."); + } } extrasMember = null; @@ -1392,7 +1401,7 @@ private Dictionary> _GetDeserializedClassMembers ( } } - if (classAttribute == null || !classAttribute.options.ShouldIgnoreProperties ()) { + if (objectAttribute == null || !objectAttribute.options.ShouldIgnoreProperties ()) { foreach (var propertyInfo in classType.GetProperties(flags)) { if (extrasMember == null) { if (Util.IsJSONExtrasMember (propertyInfo, out extrasAttribute)) { diff --git a/Assets/Plugins/UnityJSON/Enums.cs b/Assets/Plugins/UnityJSON/Enums.cs index 0b16345..a77ed86 100644 --- a/Assets/Plugins/UnityJSON/Enums.cs +++ b/Assets/Plugins/UnityJSON/Enums.cs @@ -116,7 +116,20 @@ public enum ObjectOptions /// keys, then an exception is thrown. This option prevents the /// deserializer from throwing that exception. /// - IgnoreUnknownKey = 1 << 2 + IgnoreUnknownKey = 1 << 2, + + /// + /// The class or the struct is handled as a tuple (JSON array) rather + /// than a dictionary. This automatically ignores properties both for + /// serialization and deserialization. For serialization, the fields are + /// serialized in the order they are defined in an array without keys. + /// As for deserialization, the elements are passed to the constructor + /// in the order they are defined. Deserialization does not take place + /// for other fields and properties. + /// + /// Tuple formatted classes and structs do not support JSON extras. + /// + TupleFormat = 1 << 3 | IgnoreProperties, } /// @@ -225,7 +238,7 @@ public static bool IsDeserialized (this NodeOptions options) /// public static bool ShouldSerializeNull (this NodeOptions options) { - return (options & NodeOptions.SerializeNull) != 0; + return (options & NodeOptions.SerializeNull) == NodeOptions.SerializeNull; } /// @@ -233,7 +246,7 @@ public static bool ShouldSerializeNull (this NodeOptions options) /// public static bool ShouldIgnoreTypeMismatch (this NodeOptions options) { - return (options & NodeOptions.IgnoreTypeMismatch) != 0; + return (options & NodeOptions.IgnoreTypeMismatch) == NodeOptions.IgnoreTypeMismatch; } /// @@ -241,7 +254,7 @@ public static bool ShouldIgnoreTypeMismatch (this NodeOptions options) /// public static bool ShouldIgnoreUnknownType (this NodeOptions options) { - return (options & NodeOptions.IgnoreInstantiationError) != 0; + return (options & NodeOptions.IgnoreInstantiationError) == NodeOptions.IgnoreInstantiationError; } /// @@ -257,7 +270,7 @@ public static bool ShouldAssignNull (this NodeOptions options) /// public static bool ShouldReplaceWithDeserialized (this NodeOptions options) { - return (options & NodeOptions.ReplaceDeserialized) != 0; + return (options & NodeOptions.ReplaceDeserialized) == NodeOptions.ReplaceDeserialized; } /// @@ -265,7 +278,7 @@ public static bool ShouldReplaceWithDeserialized (this NodeOptions options) /// public static bool ShouldIgnoreProperties (this ObjectOptions options) { - return (options & ObjectOptions.IgnoreProperties) != 0; + return (options & ObjectOptions.IgnoreProperties) == ObjectOptions.IgnoreProperties; } /// @@ -284,12 +297,20 @@ public static bool ShouldThrowAtUnknownKey (this ObjectOptions options) return (options & ObjectOptions.IgnoreUnknownKey) == 0; } + /// + /// Returns true if ObjectOptions.TupleFormat is not set. + /// + public static bool ShouldUseTupleFormat (this ObjectOptions options) + { + return (options & ObjectOptions.TupleFormat) == ObjectOptions.TupleFormat; + } + /// /// Returns true if ObjectTypes.String is set. /// public static bool SupportsString (this ObjectTypes types) { - return (types & ObjectTypes.String) != 0; + return (types & ObjectTypes.String) == ObjectTypes.String; } /// @@ -297,7 +318,7 @@ public static bool SupportsString (this ObjectTypes types) /// public static bool SupportsBool (this ObjectTypes types) { - return (types & ObjectTypes.Bool) != 0; + return (types & ObjectTypes.Bool) == ObjectTypes.Bool; } /// @@ -305,7 +326,7 @@ public static bool SupportsBool (this ObjectTypes types) /// public static bool SupportsNumber (this ObjectTypes types) { - return (types & ObjectTypes.Number) != 0; + return (types & ObjectTypes.Number) == ObjectTypes.Number; } /// @@ -313,7 +334,7 @@ public static bool SupportsNumber (this ObjectTypes types) /// public static bool SupportsArray (this ObjectTypes types) { - return (types & ObjectTypes.Array) != 0; + return (types & ObjectTypes.Array) == ObjectTypes.Array; } /// @@ -321,7 +342,7 @@ public static bool SupportsArray (this ObjectTypes types) /// public static bool SupportsDictionary (this ObjectTypes types) { - return (types & ObjectTypes.Dictionary) != 0; + return (types & ObjectTypes.Dictionary) == ObjectTypes.Dictionary; } /// @@ -329,7 +350,7 @@ public static bool SupportsDictionary (this ObjectTypes types) /// public static bool SupportsCustom (this ObjectTypes types) { - return (types & ObjectTypes.Custom) != 0; + return (types & ObjectTypes.Custom) == ObjectTypes.Custom; } } } diff --git a/Assets/Plugins/UnityJSON/Instantiater.cs b/Assets/Plugins/UnityJSON/Instantiater.cs index 6f8ec68..5b4b0a3 100644 --- a/Assets/Plugins/UnityJSON/Instantiater.cs +++ b/Assets/Plugins/UnityJSON/Instantiater.cs @@ -75,6 +75,10 @@ public class Instantiater { public static readonly Instantiater Default = new Instantiater (); + protected Instantiater () + { + } + /// /// Instantiates an instance of a type. First, TryInstantiate method is /// called for custom instantiation. If that fails, then the class is queried for @@ -252,22 +256,28 @@ private InstantiationData _Instantiate ( return instantiationData; } + if (node.IsNull || node.Tag == JSONNodeType.None) { + return InstantiationData.Null; + } + 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 }; + if (node.IsObject) { + 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; } - return instantiationData; } } @@ -295,6 +305,19 @@ private InstantiationData _InstantiateWithConstructor ( NodeOptions options, Deserializer deserializer) { + if (node.IsNull || node.Tag == JSONNodeType.None) { + return InstantiationData.Null; + } + + JSONObjectAttribute objectAttribute = Util.GetAttribute (targetType); + bool useTupleFormat = objectAttribute != null + ? objectAttribute.options.ShouldUseTupleFormat () : false; + if (useTupleFormat && !node.IsArray) { + throw new InstantiationException ("Expected JSON array, found " + node.Tag); + } else if (!useTupleFormat && !node.IsObject) { + throw new InstantiationException ("Expected JSON object, found " + node.Tag); + } + ConstructorInfo[] constructors = targetType.GetConstructors ( BindingFlags.Instance | BindingFlags.Public | @@ -302,12 +325,12 @@ private InstantiationData _InstantiateWithConstructor ( foreach (ConstructorInfo constructorInfo in constructors) { var constructorAttribute = Util.GetAttribute (constructorInfo); if (constructorAttribute != null) { - return _InstantiateWithConstructor (node, constructorInfo, deserializer); + return _InstantiateWithConstructor (node, constructorInfo, deserializer, useTupleFormat); } } try { - InstantiationData instantiationData = new InstantiationData(); + InstantiationData instantiationData = new InstantiationData (); instantiationData.instantiatedObject = Activator.CreateInstance (targetType); instantiationData.needsDeserialization = node.Count != 0; return instantiationData; @@ -320,7 +343,8 @@ private InstantiationData _InstantiateWithConstructor ( private InstantiationData _InstantiateWithConstructor ( JSONNode node, ConstructorInfo constructorInfo, - Deserializer deserializer) + Deserializer deserializer, + bool useTupleFormat) { ParameterInfo[] parameters = constructorInfo.GetParameters (); object[] parameterValues = new object[parameters.Length]; @@ -332,23 +356,27 @@ private InstantiationData _InstantiateWithConstructor ( string key = nodeAttribute != null && nodeAttribute.key != null ? nodeAttribute.key : parameters [i].Name; + JSONNode parameterNode = useTupleFormat ? node [i] : node [key]; + ObjectTypes restrictedTypes = restrictAttribute == null ? ObjectTypes.JSON : restrictAttribute.types; Type[] customTypes = restrictAttribute == null ? null : restrictAttribute.customTypes; parameterValues [i] = deserializer.Deserialize ( - node [key], + parameterNode, parameters [i].ParameterType, nodeAttribute == null ? NodeOptions.Default : nodeAttribute.options, restrictedTypes, customTypes); - ignoredKeys.Add (key); + if (!useTupleFormat) { + ignoredKeys.Add (key); + } } - InstantiationData instantiationData = new InstantiationData(); + InstantiationData instantiationData = new InstantiationData (); instantiationData.instantiatedObject = constructorInfo.Invoke (parameterValues); - instantiationData.needsDeserialization = ignoredKeys.Count != node.Count; + instantiationData.needsDeserialization = !useTupleFormat && ignoredKeys.Count != node.Count; instantiationData.ignoredKeys = ignoredKeys; return instantiationData; } diff --git a/Assets/Plugins/UnityJSON/JSON.cs b/Assets/Plugins/UnityJSON/JSON.cs index 8605035..44c1fd8 100644 --- a/Assets/Plugins/UnityJSON/JSON.cs +++ b/Assets/Plugins/UnityJSON/JSON.cs @@ -30,6 +30,26 @@ public static string Serialize ( return serializer.Serialize (obj, options); } + /// + /// Serializes the given object into JSON string. Throws an + /// error if the object is null. + /// + /// Object to be serialized. + /// Custom serializer. Throws an error if + /// null. + public static string Serialize ( + object obj, + Serializer serializer) + { + if (obj == null) { + throw new ArgumentNullException ("obj"); + } + if (serializer == null) { + throw new ArgumentNullException ("serializer"); + } + return serializer.Serialize (obj); + } + /// /// Serializes the object into JSON string. /// @@ -45,6 +65,19 @@ public static string ToJSONString ( return Serialize (obj, options, serializer); } + /// + /// Serializes the object into JSON string. + /// + /// Object to be serialized. + /// Custom serializer. Throws an error if + /// null. + public static string ToJSONString ( + this object obj, + Serializer serializer) + { + return Serialize (obj, serializer); + } + /// /// Deserializes an object of the generic type from /// the given JSON string. Throws an exception if the string @@ -74,6 +107,33 @@ public static T Deserialize ( return (T)deserializer.Deserialize (node, typeof(T), options); } + /// + /// 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. + /// Custom deserializer. Throws an error if + /// null. + /// The type of object to deserialize. + public static T Deserialize ( + string jsonString, + Deserializer deserializer) + { + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + if (deserializer == null) { + throw new ArgumentNullException ("deserializer"); + } + + 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)); + } + /// /// Deserializes a JSON string on a previously existing object. /// Throws an exception if the string is null or not a valid JSON string. @@ -106,6 +166,36 @@ public static void DeserializeOn ( deserializer.DeserializeOn (obj, node, 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. + /// Custom deserializer. Throws an error if + /// null. + public static void DeserializeOn ( + object obj, + string jsonString, + Deserializer deserializer) + { + if (obj == null) { + throw new ArgumentNullException ("obj"); + } + if (jsonString == null) { + throw new ArgumentNullException ("jsonString"); + } + if (deserializer == null) { + throw new ArgumentNullException ("deserializer"); + } + + 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); + } + /// /// Deserializes a JSON string on the previously existing object. /// Throws an exception if the string is null or not a valid JSON string. @@ -123,5 +213,21 @@ public static void FeedJSON ( { DeserializeOn (obj, jsonString, options, deserializer); } + + /// + /// 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. + /// Custom deserializer. Throws an error if + /// null. + public static void FeedJSON ( + this object obj, + string jsonString, + Deserializer deserializer) + { + DeserializeOn (obj, jsonString, deserializer); + } } } diff --git a/Assets/Plugins/UnityJSON/Serializer.cs b/Assets/Plugins/UnityJSON/Serializer.cs index fd4a286..8966a84 100644 --- a/Assets/Plugins/UnityJSON/Serializer.cs +++ b/Assets/Plugins/UnityJSON/Serializer.cs @@ -18,7 +18,7 @@ public class Serializer private const string _kTrue = "true"; private const string _kFalse = "false"; - private static Serializer _default = new Serializer(); + private static Serializer _default = new Serializer (); /// /// The default serializer to be used when no serializer is given. @@ -46,7 +46,7 @@ public static Serializer Default { /// public bool useUndefinedForNull = false; - private Serializer () + protected Serializer () { } @@ -278,13 +278,19 @@ public string SerializeBounds (Bounds bounds) /// /// Serializes an object by its fields and properties. This will /// ignore custom serializations of the object (see Serializer.TrySerialize - /// and ISerializable.Serialize). + /// and ISerializable.Serialize). This will throw an argument exception if + /// the object is a non-struct value type (primitives and enums). /// public string SerializeByParts (object obj, NodeOptions options = NodeOptions.Default) { if (obj == null) { return SerializeNull (options); } + Type type = obj.GetType (); + if (type.IsPrimitive || type.IsEnum) { + throw new ArgumentException ("Cannot serialize non-struct value types by parts."); + } + return _SerializeCustom (obj, options); } @@ -382,29 +388,35 @@ private string _SerializeCustom (object obj, NodeOptions options) }; Type type = obj.GetType (); - JSONObjectAttribute classAttribute = Util.GetAttribute (type); + JSONObjectAttribute objectAttribute = Util.GetAttribute (type); var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - if (classAttribute != null && !classAttribute.options.ShouldIgnoreStatic ()) { - flags |= BindingFlags.Static; + bool useTupleFormat = false; + if (objectAttribute != null) { + if (!objectAttribute.options.ShouldIgnoreStatic ()) { + flags |= BindingFlags.Static; + } + if (objectAttribute.options.ShouldUseTupleFormat ()) { + useTupleFormat = true; + } } enumerable = enumerable.Concat ( from f in type.GetFields (flags) where isNotExtras (f) && _IsValidFieldInfo (f) - select _SerializeCustomField (obj, f)); + select _SerializeCustomField (obj, f, useTupleFormat)); - if (classAttribute == null || !classAttribute.options.ShouldIgnoreProperties ()) { + if (objectAttribute == null || !objectAttribute.options.ShouldIgnoreProperties ()) { enumerable = enumerable.Concat ( from p in type.GetProperties (flags) where isNotExtras (p) && _IsValidPropertyInfo (p) - select _SerializeCustomProperty (obj, p)); + select _SerializeCustomProperty (obj, p, useTupleFormat)); } // Serialize all properties and fields. var result = _Join (enumerable, o => o as string); // Serialize the extras if there are any. - if (extrasMember != null) { + if (!useTupleFormat && extrasMember != null) { var extras = Util.GetMemberValue (extrasMember, obj) as IEnumerable; if (extras != null) { result += (result == "" ? "" : ",") @@ -415,8 +427,12 @@ where isNotExtras (p) && _IsValidPropertyInfo (p) if (listener != null) { listener.OnSerializationSucceeded (this); } - return "{" + result + "}"; - ; + + if (useTupleFormat) { + return "[" + result + "]"; + } else { + return "{" + result + "}"; + } } catch (Exception exception) { if (listener != null) { listener.OnSerializationFailed (this); @@ -471,17 +487,17 @@ private string _Join (IEnumerable enumerable, Func serializer) return result; } - private string _SerializeCustomField (object obj, FieldInfo fieldInfo) + private string _SerializeCustomField (object obj, FieldInfo fieldInfo, bool useTupleFormat) { - return _SerializeCustomMember (fieldInfo, fieldInfo.GetValue (obj)); + return _SerializeCustomMember (fieldInfo, fieldInfo.GetValue (obj), useTupleFormat); } - private string _SerializeCustomProperty (object obj, PropertyInfo propertyInfo) + private string _SerializeCustomProperty (object obj, PropertyInfo propertyInfo, bool useTupleFormat) { - return _SerializeCustomMember (propertyInfo, propertyInfo.GetValue (obj, null)); + return _SerializeCustomMember (propertyInfo, propertyInfo.GetValue (obj, null), useTupleFormat); } - private string _SerializeCustomMember (MemberInfo keyMemberInfo, object value) + private string _SerializeCustomMember (MemberInfo keyMemberInfo, object value, bool useTupleFormat) { JSONNodeAttribute attribute = Util.GetAttribute (keyMemberInfo); NodeOptions options = attribute == null ? NodeOptions.Default : attribute.options; @@ -491,6 +507,9 @@ private string _SerializeCustomMember (MemberInfo keyMemberInfo, object value) string valueString = Serialize (value, options); if (valueString != null || options.ShouldSerializeNull ()) { + if (useTupleFormat) { + return valueString == null ? _kUndefined : valueString; + } string key = (attribute != null && attribute.key != null) ? attribute.key : keyMemberInfo.Name; return _SerializeString (key) + ":" + (valueString == null ? _kUndefined : valueString); } else { diff --git a/README.md b/README.md index cbbf015..d7be595 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ * [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) @@ -20,6 +19,9 @@ - [Custom Deserialization with Instantiater](#custom-deserialization-with-instantiater) - [Custom Deserialization with Deserializer](#custom-deserialization-with-deserializer) - [Custom Deserialization with IDeserializable](#custom-deserialization-with-ideserializable) +* [Special Types](#special-types) + - [Enums](#enums) + - [Tuples](#tuples) * [Changelog](#changelog) - [v2.0](#v20) - [v1.1](#v11) @@ -125,28 +127,8 @@ at. It uses the `ObjectOptions` enum which has the following serialization optio - 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 -``` +- TupleFormat: Handles the struct or the class as a tuple of fields. See [Tuples](#tuples) +for more detail. ### Serialization Lifecycle @@ -643,19 +625,87 @@ public class AClass : IDeserializable The classes that are deserialized with the `IDeserializable.Deserialize` method do not receive deserialization lifecycle calls from `IDeserializationListener`. +## Special Types + +### Enums + +Enums are by default serialized and deserialized directly with their member names. This process +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 +JSON.DeserializeEnum("forwardPosition") // Positions.Forward +``` + +### Tuples + +Classes and structs can also be serialized and deserialized as tuples (JSON arrays). This +is performed by adding the `ObjectOptions.TupleFormat` to a `JSONObjectAttribute` at +the class / struct declaration. Tuple formatted classes and structs always ignore properties. +When serialized, the fields are serialized in an array in the order they are declared. +When deserialized, the class / struct MUST provide a constructor with the +`JSONConstructorAttribute` where the arguments are passed directly from the array. + +```cs +[JSONObject(ObjectOptions.TupleFormat)] +public class Tuple +{ + [JSONNode(NodeOptions.SerializeNull)] + public T1 item1; + + [JSONNode(NodeOptions.SerializeNull)] + public T2 item2; + + [JSONConstructor] + public Tuple(T1 item1, T2 item2) + { + this.item1 = item1; + this.item2 = item2; + } +} + +var tuple = new Tuple(2, "value"); +tuple.ToJSONString(); // [2, "value"] + +var obj = JSON.Deserialize>>( + "[[\"this\",\"is\",\"IList\"], [3.14, 2.17]]"); +``` + +Please notice that tuple deserialization takes place at the instantiation and therefore +cannot be used together with `Deserializer.DeserializeOn`. + +UnityJSON does not use C# tuples because Unity3D does not have support for them yet. + ## Changelog +### v2.1 + +- Provides Tuple support + ### v2.0 - Bug fixes -- Added Serializer.SerializeByParts -- Added Deserializer.DeserializeByParts and deserializer methods taking JSON +- Adds Serializer.SerializeByParts +- Adds Deserializer.DeserializeByParts and deserializer methods taking JSON string arguments -- Created the class Instantiater +- Creates the class Instantiater - Allows use of RestrictTypeAttribute with constructor arguments - Introduces InstantiationData to work around ignored keys ### v1.1 -- Added `JSONConstructorAttribute` -- Fixed conditional instantiation bug: JSONNode kept the same after key removal +- Adds `JSONConstructorAttribute` +- Fixes conditional instantiation bug: JSONNode kept the same after key removal diff --git a/unityjson.unitypackage b/unityjson.unitypackage index aecfe47..57950d7 100644 Binary files a/unityjson.unitypackage and b/unityjson.unitypackage differ