diff --git a/Assets/Plugins/UnityJSON/Attributes.cs b/Assets/Plugins/UnityJSON/Attributes.cs index 8add72b..cefc2bc 100644 --- a/Assets/Plugins/UnityJSON/Attributes.cs +++ b/Assets/Plugins/UnityJSON/Attributes.cs @@ -10,7 +10,10 @@ namespace UnityJSON /// attribute, the default options are used. For private fields and /// properties, this attribute is mandatory. /// - [AttributeUsage (AttributeTargets.Field | AttributeTargets.Property)] + [AttributeUsage ( + AttributeTargets.Field | + AttributeTargets.Property | + AttributeTargets.Parameter)] public class JSONNodeAttribute : Attribute { private NodeOptions _options; @@ -320,4 +323,17 @@ public Type referenceType { get { return _reference; } } } + + /// + /// Marks a constructor that is used to deserialize objects. + /// Every class can use this attribute maximum one times. The + /// parameters can have JSONNodeAttributes. + /// + [AttributeUsage (AttributeTargets.Constructor)] + public class JSONConstructorAttribute : Attribute + { + public JSONConstructorAttribute () : base () + { + } + } } diff --git a/Assets/Plugins/UnityJSON/Deserializer.cs b/Assets/Plugins/UnityJSON/Deserializer.cs index 31888af..9ddb1b5 100644 --- a/Assets/Plugins/UnityJSON/Deserializer.cs +++ b/Assets/Plugins/UnityJSON/Deserializer.cs @@ -177,7 +177,7 @@ public Nullable DeserializeToNullable ( if (node == null || node.IsNull) { return null; } - return (Nullable) Deserialize (node, typeof(T), options); + return (Nullable)Deserialize (node, typeof(T), options); } /// @@ -842,11 +842,16 @@ 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 (Equals (node [condition.key].Value, condition.value.ToString ())) { + JSONNode value = node [condition.key]; if (condition.removeKey) { node.Remove (condition.key); } - return Deserialize (node, condition.referenceType, options); + object result = Deserialize (node, condition.referenceType, options); + if (condition.removeKey) { + node.Add (condition.key, value); + } + return result; } } @@ -855,17 +860,62 @@ private object _DeserializeCustom (JSONNode node, Type type, NodeOptions options return Deserialize (node, defaultAttribute.referenceType, options); } - object obj; - try { - obj = Activator.CreateInstance (type); - } catch (Exception) { - return _HandleUnknown (options, "Unknown type " + type + " cannot be instantiated."); + 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."); + } } DeserializeOn (obj, node, options); + if (removedKeys != null) { + foreach (var pair in removedKeys) { + node.Add (pair.Key, pair.Value); + } + } return obj; } + private object _CreateObjectWithConstructor ( + Type type, + ConstructorInfo constructor, + JSONNode node, + out KeyValuePair[] removedKeys) + { + 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); + } + return Activator.CreateInstance (type, parameterValues); + } + private void _FeedCustom (object filledObject, JSONNode node, NodeOptions options) { if (filledObject is IDeserializable) { diff --git a/Assets/Plugins/UnityJSON/Interfaces.cs b/Assets/Plugins/UnityJSON/Interfaces.cs index 9c7c9e9..e7f5aae 100644 --- a/Assets/Plugins/UnityJSON/Interfaces.cs +++ b/Assets/Plugins/UnityJSON/Interfaces.cs @@ -40,7 +40,7 @@ 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); + void Deserialize (JSONNode node, Deserializer deserializer); } /// @@ -53,19 +53,19 @@ public interface ISerializationListener /// This call will always be followed by either OnSerializationSucceeded /// or OnSerializationFailed but not both. /// - void OnSerializationWillBegin(Serializer serializer); + void OnSerializationWillBegin (Serializer serializer); /// /// Called immediately after a successful completion of this /// object's serialization. /// - void OnSerializationSucceeded(Serializer serializer); + 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); + void OnSerializationFailed (Serializer serializer); } /// @@ -78,18 +78,18 @@ public interface IDeserializationListener /// This call will always be followed by either OnDeserializationSucceeded /// or OnDeserializationFailed but not both. /// - void OnDeserializationWillBegin(Deserializer deserializer); + void OnDeserializationWillBegin (Deserializer deserializer); /// /// Called immediately after a successful completion of this /// object's deserialization. /// - void OnDeserializationSucceeded(Deserializer deserializer); + 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); + void OnDeserializationFailed (Deserializer deserializer); } } diff --git a/Assets/Plugins/UnityJSON/Serializer.cs b/Assets/Plugins/UnityJSON/Serializer.cs index 5a92777..73b4303 100644 --- a/Assets/Plugins/UnityJSON/Serializer.cs +++ b/Assets/Plugins/UnityJSON/Serializer.cs @@ -49,7 +49,7 @@ public static Serializer Simple { /// public bool useUndefinedForNull = false; - private Serializer() + private Serializer () { } @@ -98,7 +98,7 @@ public string Serialize (object obj, NodeOptions options = NodeOptions.Default) Type type = obj.GetType (); if (type.IsValueType) { if (type.IsEnum) { - result = _SerializeEnum ((Enum) obj); + result = _SerializeEnum ((Enum)obj); } else if (type.IsPrimitive) { if (obj is bool) { result = SerializeBool ((bool)obj); @@ -236,7 +236,7 @@ public string SerializeVector3 (Vector3 vector) public string SerializeVector4 (Vector4 vector) { return "{\"x\":" + vector.x + ",\"y\":" + vector.y - + ",\"z\":" + vector.z + ",\"w\":" + vector.w + "}"; + + ",\"z\":" + vector.z + ",\"w\":" + vector.w + "}"; } /// @@ -245,7 +245,7 @@ public string SerializeVector4 (Vector4 vector) public string SerializeQuaternion (Quaternion quaternion) { return "{\"x\":" + quaternion.x + ",\"y\":" + quaternion.y - + ",\"z\":" + quaternion.z + ",\"w\":" + quaternion.w + "}"; + + ",\"z\":" + quaternion.z + ",\"w\":" + quaternion.w + "}"; } /// @@ -254,7 +254,7 @@ public string SerializeQuaternion (Quaternion quaternion) public string SerializeColor (Color color) { return "{\"r\":" + color.r + ",\"g\":" + color.g - + ",\"b\":" + color.b + ",\"a\":" + color.a + "}"; + + ",\"b\":" + color.b + ",\"a\":" + color.a + "}"; } /// @@ -263,7 +263,7 @@ public string SerializeColor (Color color) private string SerializeRect (Rect rect) { return "{\"x\":" + rect.x + ",\"y\":" + rect.y - + ",\"width\":" + rect.width + ",\"height\":" + rect.height + "}"; + + ",\"width\":" + rect.width + ",\"height\":" + rect.height + "}"; } /// @@ -272,7 +272,7 @@ private string SerializeRect (Rect rect) private string SerializeBounds (Bounds bounds) { return "{\"center\":" + SerializeVector3 (bounds.center) - + ",\"extents\":" + SerializeVector3 (bounds.extents) + "}"; + + ",\"extents\":" + SerializeVector3 (bounds.extents) + "}"; } private string _SerializeString (string stringValue) @@ -286,7 +286,7 @@ private string _SerializeEnum (Enum obj) JSONEnumAttribute enumAttribute = Util.GetAttribute (type); if (enumAttribute != null) { if (enumAttribute.useIntegers) { - return (Convert.ToInt32(obj)).ToString (); + return (Convert.ToInt32 (obj)).ToString (); } else { string formatted = _FormatEnumMember (obj.ToString (), enumAttribute.format); if (enumAttribute.prefix != null) { @@ -395,14 +395,15 @@ where isNotExtras (p) && _IsValidPropertyInfo (p) var extras = Util.GetMemberValue (extrasMember, obj) as IEnumerable; if (extras != null) { result += (result == "" ? "" : ",") - + _SerializeEnumarable (extras, extrasAttribute.options); + + _SerializeEnumarable (extras, extrasAttribute.options); } } if (listener != null) { listener.OnSerializationSucceeded (this); } - return "{" + result + "}";; + return "{" + result + "}"; + ; } catch (Exception exception) { if (listener != null) { listener.OnSerializationFailed (this); diff --git a/Assets/Plugins/UnityJSON/Util.cs b/Assets/Plugins/UnityJSON/Util.cs index 30df74e..f388e13 100644 --- a/Assets/Plugins/UnityJSON/Util.cs +++ b/Assets/Plugins/UnityJSON/Util.cs @@ -13,6 +13,12 @@ internal static T GetAttribute (MemberInfo info) where T : Attribute return (attributes == null || attributes.Length == 0) ? null : attributes [0] as T; } + internal static T GetAttribute (ParameterInfo 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 ? diff --git a/README.md b/README.md index 5b082c6..e1fe681 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,13 @@ * [Deserialization](#deserialization) - [Deserialized Types](#deserialized-types) - [Deserialization of Extra Nodes](#deserialization-of-extra-nodes) - - [Deserialization with Inheritance](#deserialization-with-inheritance) + - [Inheritance](#inheritance) + - [Constructors](#constructors) - [Deserialization Lifecycle](#deserialization-lifecycle) - [Custom Deserialization with Deserializer](#custom-deserialization-with-deserializer) - [Custom Deserialization with IDeserializable](#custom-deserialization-with-ideserializable) +* [Changelog](#changelog) + - [v1.1](#v11) ## Features @@ -445,7 +448,7 @@ You can also use `RestrictTypeAttribute` to restrict the supported types or use types. The extras are also used for the serialization and are serialized on the same level as the object. -### Deserialization with Inheritance +### Inheritance You may want to provide deserialization to interface or abstract class targets. One option would be to use a custom deserializer @@ -484,6 +487,26 @@ class C : I I obj = JSON.Deserialize("{\"type\":1}"); // obj is of type B. ``` +### Constructors + +You can deserialize objects with custom constructors using the `JSONConstructor` +attribute. The constructor arguments can have additional `JSONNode` attributes. Be +aware that only one constructor can have this attribute. + +```cs +class AClass +{ + private T _arg; + + [JSONConstructor] + public AClass([JSONNode(key = "field")] T argument) + { + _arg = argument; + } +} +var obj = Deserialize>("{\"field\":1}"); +``` + ### Deserialization Lifecycle You can listen to the deserialization lifecycle of an object by implementing the @@ -596,3 +619,10 @@ public class AClass : IDeserializable The classes that are deserialized with the `IDeserializable.Deserialize` method do not receive deserialization lifecycle calls from `IDeserializationListener`. + +## Changelog + +### v1.1 + +- Added `JSONConstructorAttribute` +- Fixed conditional instantiation bug: JSONNode kept the same after key removal diff --git a/unityjson.unitypackage b/unityjson.unitypackage index 2ae240b..5cbc581 100644 Binary files a/unityjson.unitypackage and b/unityjson.unitypackage differ