Skip to content

Commit

Permalink
feat: create JSON constructor attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
adragonite committed Sep 21, 2017
1 parent d682704 commit bb7b7b3
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 28 deletions.
18 changes: 17 additions & 1 deletion Assets/Plugins/UnityJSON/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ namespace UnityJSON
/// attribute, the default options are used. For private fields and
/// properties, this attribute is mandatory.
/// </summary>
[AttributeUsage (AttributeTargets.Field | AttributeTargets.Property)]
[AttributeUsage (
AttributeTargets.Field |
AttributeTargets.Property |
AttributeTargets.Parameter)]
public class JSONNodeAttribute : Attribute
{
private NodeOptions _options;
Expand Down Expand Up @@ -320,4 +323,17 @@ public Type referenceType {
get { return _reference; }
}
}

/// <summary>
/// Marks a constructor that is used to deserialize objects.
/// Every class can use this attribute maximum one times. The
/// parameters can have JSONNodeAttributes.
/// </summary>
[AttributeUsage (AttributeTargets.Constructor)]
public class JSONConstructorAttribute : Attribute
{
public JSONConstructorAttribute () : base ()
{
}
}
}
66 changes: 58 additions & 8 deletions Assets/Plugins/UnityJSON/Deserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public Nullable<T> DeserializeToNullable<T> (
if (node == null || node.IsNull) {
return null;
}
return (Nullable<T>) Deserialize (node, typeof(T), options);
return (Nullable<T>)Deserialize (node, typeof(T), options);
}

/// <summary>
Expand Down Expand Up @@ -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;
}
}

Expand All @@ -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<string, JSONNode>[] removedKeys = null;
ConstructorInfo[] constructors = type.GetConstructors (
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic);
foreach (ConstructorInfo constructor in constructors) {
var constructorAttribute = Util.GetAttribute<JSONConstructorAttribute> (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<string, JSONNode>[] removedKeys)
{
ParameterInfo[] parameters = constructor.GetParameters ();
object[] parameterValues = new object[parameters.Length];
removedKeys = new KeyValuePair<string, JSONNode>[parameterValues.Length];

for (int i = 0; i < parameterValues.Length; i++) {
var parameterAttribute = Util.GetAttribute<JSONNodeAttribute> (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<string, JSONNode> (key, node [key]);
node.Remove (key);
}
return Activator.CreateInstance (type, parameterValues);
}

private void _FeedCustom (object filledObject, JSONNode node, NodeOptions options)
{
if (filledObject is IDeserializable) {
Expand Down
14 changes: 7 additions & 7 deletions Assets/Plugins/UnityJSON/Interfaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public interface IDeserializable
/// Feeds the JSON node into the object. You can use the
/// helper methods from the deserializer.
/// </summary>
void Deserialize(JSONNode node, Deserializer deserializer);
void Deserialize (JSONNode node, Deserializer deserializer);
}

/// <summary>
Expand All @@ -53,19 +53,19 @@ public interface ISerializationListener
/// This call will always be followed by either OnSerializationSucceeded
/// or OnSerializationFailed but not both.
/// </summary>
void OnSerializationWillBegin(Serializer serializer);
void OnSerializationWillBegin (Serializer serializer);

/// <summary>
/// Called immediately after a successful completion of this
/// object's serialization.
/// </summary>
void OnSerializationSucceeded(Serializer serializer);
void OnSerializationSucceeded (Serializer serializer);

/// <summary>
/// Called when the serialization of the object fails for any
/// reason. This will be called just before throwing the exception.
/// </summary>
void OnSerializationFailed(Serializer serializer);
void OnSerializationFailed (Serializer serializer);
}

/// <summary>
Expand All @@ -78,18 +78,18 @@ public interface IDeserializationListener
/// This call will always be followed by either OnDeserializationSucceeded
/// or OnDeserializationFailed but not both.
/// </summary>
void OnDeserializationWillBegin(Deserializer deserializer);
void OnDeserializationWillBegin (Deserializer deserializer);

/// <summary>
/// Called immediately after a successful completion of this
/// object's deserialization.
/// </summary>
void OnDeserializationSucceeded(Deserializer deserializer);
void OnDeserializationSucceeded (Deserializer deserializer);

/// <summary>
/// Called when the deserialization of the object fails for any
/// reason. This will be called just before throwing the exception.
/// </summary>
void OnDeserializationFailed(Deserializer deserializer);
void OnDeserializationFailed (Deserializer deserializer);
}
}
21 changes: 11 additions & 10 deletions Assets/Plugins/UnityJSON/Serializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static Serializer Simple {
/// </summary>
public bool useUndefinedForNull = false;

private Serializer()
private Serializer ()
{
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 + "}";
}

/// <summary>
Expand All @@ -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 + "}";
}

/// <summary>
Expand All @@ -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 + "}";
}

/// <summary>
Expand All @@ -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 + "}";
}

/// <summary>
Expand All @@ -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)
Expand All @@ -286,7 +286,7 @@ private string _SerializeEnum (Enum obj)
JSONEnumAttribute enumAttribute = Util.GetAttribute<JSONEnumAttribute> (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) {
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions Assets/Plugins/UnityJSON/Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ internal static T GetAttribute<T> (MemberInfo info) where T : Attribute
return (attributes == null || attributes.Length == 0) ? null : attributes [0] as T;
}

internal static T GetAttribute<T> (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 ?
Expand Down
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -484,6 +487,26 @@ class C : I
I obj = JSON.Deserialize<I>("{\"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<T>
{
private T _arg;

[JSONConstructor]
public AClass([JSONNode(key = "field")] T argument)
{
_arg = argument;
}
}
var obj = Deserialize<AClass<int>>("{\"field\":1}");
```

### Deserialization Lifecycle

You can listen to the deserialization lifecycle of an object by implementing the
Expand Down Expand Up @@ -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
Binary file modified unityjson.unitypackage
Binary file not shown.

0 comments on commit bb7b7b3

Please sign in to comment.