Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Json Serialization #2794

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Neo.Json/JNumber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ public class JNumber : JToken
/// <summary>
/// Represents the largest safe integer in JSON.
/// </summary>
public static readonly long MAX_SAFE_INTEGER = (long)Math.Pow(2, 53) - 1;
public const long MAX_SAFE_INTEGER = (1L << 53) - 1;

/// <summary>
/// Represents the smallest safe integer in JSON.
/// </summary>
public static readonly long MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER;
public const long MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER;

/// <summary>
/// Gets the value of the JSON token.
Expand Down
2 changes: 2 additions & 0 deletions src/Neo.Json/JString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.Json;

Expand Down Expand Up @@ -88,6 +89,7 @@ public static implicit operator JString(Enum value)
return new JString(value.ToString());
}

[return: NotNullIfNotNull("value")]
public static implicit operator JString?(string? value)
{
return value == null ? null : new JString(value);
Expand Down
2 changes: 2 additions & 0 deletions src/Neo.Json/JToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using static Neo.Json.Utility;

Expand Down Expand Up @@ -297,6 +298,7 @@ public static implicit operator JToken(double value)
return (JNumber)value;
}

[return: NotNullIfNotNull("value")]
public static implicit operator JToken?(string? value)
{
return (JString?)value;
Expand Down
1 change: 1 addition & 0 deletions src/Neo.Json/Neo.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<PackageTags>NEO;JSON</PackageTags>
</PropertyGroup>

Expand Down
33 changes: 33 additions & 0 deletions src/Neo.Json/Serialization/ArraySerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (C) 2015-2022 The Neo Project.
//
// The Neo.Json is free software distributed under the MIT software license,
// see the accompanying file LICENSE in the main directory of the
// project or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using System.Diagnostics.CodeAnalysis;

namespace Neo.Json.Serialization;

class ArraySerializer<T> : Serializer<T?[]?>
{
private static readonly Serializer<T> elementSerializer = GetSerializer<T>();

[return: NotNullIfNotNull("obj")]
protected internal override JArray? Serialize(T?[]? obj)
{
if (obj is null) return null;
return obj.Select(elementSerializer.Serialize).ToArray();
}

[return: NotNullIfNotNull("json")]
protected internal override T?[]? Deserialize(JToken? json) => json switch
{
JArray array => array.Select(elementSerializer.Deserialize).ToArray(),
null => null,
_ => throw new FormatException()
};
}
18 changes: 18 additions & 0 deletions src/Neo.Json/Serialization/BooleanSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (C) 2015-2022 The Neo Project.
//
// The Neo.Json is free software distributed under the MIT software license,
// see the accompanying file LICENSE in the main directory of the
// project or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

namespace Neo.Json.Serialization;

class BooleanSerializer : Serializer<bool>
{
protected internal override JBoolean Serialize(bool obj) => obj;

protected internal override bool Deserialize(JToken? json) => json!.GetBoolean();
}
22 changes: 22 additions & 0 deletions src/Neo.Json/Serialization/ByteArraySerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (C) 2015-2022 The Neo Project.
//
// The Neo.Json is free software distributed under the MIT software license,
// see the accompanying file LICENSE in the main directory of the
// project or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using System.Diagnostics.CodeAnalysis;

namespace Neo.Json.Serialization;

class ByteArraySerializer : Serializer<byte[]?>
{
[return: NotNullIfNotNull("obj")]
protected internal override JString? Serialize(byte[]? obj) => obj is null ? null : Convert.ToBase64String(obj);

[return: NotNullIfNotNull("json")]
protected internal override byte[]? Deserialize(JToken? json) => json is null ? null : Convert.FromBase64String(json.GetString());
}
73 changes: 73 additions & 0 deletions src/Neo.Json/Serialization/CompositeSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (C) 2015-2022 The Neo Project.
//
// The Neo.Json is free software distributed under the MIT software license,
// see the accompanying file LICENSE in the main directory of the
// project or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Serialization;

namespace Neo.Json.Serialization;

class CompositeSerializer<T> : Serializer<T>
{
private static readonly (PropertyInfo, Serializer, Func<T, Serializer, JToken?>, Action<T, Serializer, JToken?>)[] properties;

static CompositeSerializer()
{
properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(p => p.GetMethod is not null && p.SetMethod is not null)
.Select(p =>
{
Serializer serializer = GetSerializer(p.PropertyType);
Type sType = serializer.GetType();
MethodInfo serializeMethod = sType.GetMethod(nameof(Serialize), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
MethodInfo deserializeMethod = sType.GetMethod(nameof(Deserialize), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
var instanceExpr = Expression.Parameter(typeof(T));
var serializerExpr = Expression.Parameter(typeof(Serializer));
var callExpr = Expression.Call(instanceExpr, p.GetMethod!);
var convertExpr = Expression.Convert(serializerExpr, sType);
callExpr = Expression.Call(convertExpr, serializeMethod, callExpr);
var getterExpr = Expression.Lambda<Func<T, Serializer, JToken?>>(callExpr, instanceExpr, serializerExpr);
var valueExpr = Expression.Parameter(typeof(JToken));
callExpr = Expression.Call(convertExpr, deserializeMethod, valueExpr);
callExpr = Expression.Call(instanceExpr, p.SetMethod!, callExpr);
var setterExpr = Expression.Lambda<Action<T, Serializer, JToken?>>(callExpr, instanceExpr, serializerExpr, valueExpr);
return (p, serializer, getterExpr.Compile(), setterExpr.Compile());
})
.ToArray();
}

[return: NotNullIfNotNull("obj")]
protected internal override JObject? Serialize(T? obj)
{
if (obj is null) return null;
JObject result = new();
foreach (var (property, serializer, getter, _) in properties)
result[property.Name] = getter(obj, serializer);
return result;
}

[return: NotNullIfNotNull("json")]
protected internal override T? Deserialize(JToken? json)
{
switch (json)
{
case JObject obj:
T result = (T)FormatterServices.GetUninitializedObject(typeof(T));
foreach (var (property, serializer, _, setter) in properties)
setter(result, serializer, obj[property.Name]);
return result;
case null:
return default;
default:
throw new FormatException();
}
}
}
18 changes: 18 additions & 0 deletions src/Neo.Json/Serialization/EnumSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (C) 2015-2022 The Neo Project.
//
// The Neo.Json is free software distributed under the MIT software license,
// see the accompanying file LICENSE in the main directory of the
// project or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

namespace Neo.Json.Serialization;

class EnumSerializer<T> : Serializer<T> where T : unmanaged, Enum
{
protected internal override JString Serialize(T obj) => obj.ToString();

protected internal override T Deserialize(JToken? json) => json!.GetEnum<T>();
}
23 changes: 23 additions & 0 deletions src/Neo.Json/Serialization/Int64Serializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (C) 2015-2022 The Neo Project.
//
// The Neo.Json is free software distributed under the MIT software license,
// see the accompanying file LICENSE in the main directory of the
// project or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

namespace Neo.Json.Serialization;

class Int64Serializer : Serializer<long>
{
protected internal override JToken Serialize(long obj) => obj >= JNumber.MIN_SAFE_INTEGER && obj <= JNumber.MAX_SAFE_INTEGER ? obj : obj.ToString();

protected internal override long Deserialize(JToken? json) => json switch
{
JNumber n => (long)n.Value,
JString s => long.Parse(s.Value),
_ => throw new FormatException()
};
}
23 changes: 23 additions & 0 deletions src/Neo.Json/Serialization/IntegerSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (C) 2015-2022 The Neo Project.
//
// The Neo.Json is free software distributed under the MIT software license,
// see the accompanying file LICENSE in the main directory of the
// project or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

namespace Neo.Json.Serialization;

class IntegerSerializer<T> : Serializer<T> where T : unmanaged
{
protected internal override JToken Serialize(T obj) => (JToken)Convert.ToDouble(obj);

protected internal override T Deserialize(JToken? json) => json switch
{
JNumber n => (T)Convert.ChangeType(n.Value, typeof(T)),
JString s => (T)Convert.ChangeType(long.Parse(s.Value), typeof(T)),
_ => throw new FormatException()
};
}
90 changes: 90 additions & 0 deletions src/Neo.Json/Serialization/Serializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (C) 2015-2022 The Neo Project.
//
// The Neo.Json is free software distributed under the MIT software license,
// see the accompanying file LICENSE in the main directory of the
// project or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using System.Diagnostics.CodeAnalysis;

namespace Neo.Json.Serialization;

public abstract class Serializer
{
private static readonly Dictionary<Type, Serializer> serializers = new()
{
[typeof(bool)] = new BooleanSerializer(),
[typeof(sbyte)] = new IntegerSerializer<sbyte>(),
[typeof(byte)] = new IntegerSerializer<byte>(),
[typeof(short)] = new IntegerSerializer<short>(),
[typeof(ushort)] = new IntegerSerializer<ushort>(),
[typeof(int)] = new IntegerSerializer<int>(),
[typeof(uint)] = new IntegerSerializer<uint>(),
[typeof(long)] = new Int64Serializer(),
[typeof(ulong)] = new UInt64Serializer(),
[typeof(string)] = new StringSerializer(),
[typeof(byte[])] = new ByteArraySerializer()
};

protected static Serializer GetSerializer(Type type)
{
if (!serializers.TryGetValue(type, out var serializer))
{
Type? serializerAttribute = type.CustomAttributes
.Select(p => p.AttributeType)
.FirstOrDefault(p => p.IsGenericType && p.GetGenericTypeDefinition() == typeof(SerializerAttribute<>));
if (serializerAttribute is not null)
serializer = (Serializer)Activator.CreateInstance(serializerAttribute.GenericTypeArguments[0])!;
else if (type.IsEnum)
serializer = (Serializer)Activator.CreateInstance(typeof(EnumSerializer<>).MakeGenericType(type))!;
else if (type.IsArray)
serializer = (Serializer)Activator.CreateInstance(typeof(ArraySerializer<>).MakeGenericType(type.GetElementType()!))!;
else
serializer = (Serializer)Activator.CreateInstance(typeof(CompositeSerializer<>).MakeGenericType(type))!;
serializers.Add(type, serializer);
}
return serializer;
}

protected static Serializer<T> GetSerializer<T>()
{
return (Serializer<T>)GetSerializer(typeof(T));
}

[return: NotNullIfNotNull("obj")]
public static JToken? Serialize(object? obj)
{
if (obj is null) return JToken.Null;
Serializer serializer = GetSerializer(obj.GetType());
return serializer.SerializeObject(obj);
}

public static JToken Serialize<T>(T obj) where T : struct
{
Serializer<T> serializer = GetSerializer<T>();
return serializer.Serialize(obj);
}

[return: NotNullIfNotNull("json")]
public static T? Deserialize<T>(JToken? json)
{
Serializer<T> serializer = GetSerializer<T>();
return serializer.Deserialize(json);
}

private protected abstract JToken SerializeObject(object obj);
}

public abstract class Serializer<T> : Serializer
{
private protected sealed override JToken SerializeObject(object obj) => Serialize((T)obj);

[return: NotNullIfNotNull("obj")]
protected internal abstract JToken? Serialize(T? obj);

[return: NotNullIfNotNull("json")]
protected internal abstract T? Deserialize(JToken? json);
}
16 changes: 16 additions & 0 deletions src/Neo.Json/Serialization/SerializerAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (C) 2015-2022 The Neo Project.
//
// The Neo.Json is free software distributed under the MIT software license,
// see the accompanying file LICENSE in the main directory of the
// project or http://www.opensource.org/licenses/mit-license.php
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

namespace Neo.Json.Serialization;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum, Inherited = false)]
public class SerializerAttribute<T> : Attribute where T : Serializer
{
}
Loading