diff --git a/src/Neo.Json/JNumber.cs b/src/Neo.Json/JNumber.cs
index 7e58b72567..5acf982956 100644
--- a/src/Neo.Json/JNumber.cs
+++ b/src/Neo.Json/JNumber.cs
@@ -21,12 +21,12 @@ public class JNumber : JToken
///
/// Represents the largest safe integer in JSON.
///
- public static readonly long MAX_SAFE_INTEGER = (long)Math.Pow(2, 53) - 1;
+ public const long MAX_SAFE_INTEGER = (1L << 53) - 1;
///
/// Represents the smallest safe integer in JSON.
///
- public static readonly long MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER;
+ public const long MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER;
///
/// Gets the value of the JSON token.
diff --git a/src/Neo.Json/JString.cs b/src/Neo.Json/JString.cs
index 714ff85928..f7919e5b54 100644
--- a/src/Neo.Json/JString.cs
+++ b/src/Neo.Json/JString.cs
@@ -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;
@@ -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);
diff --git a/src/Neo.Json/JToken.cs b/src/Neo.Json/JToken.cs
index 63ab9f4752..b8578350e9 100644
--- a/src/Neo.Json/JToken.cs
+++ b/src/Neo.Json/JToken.cs
@@ -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;
@@ -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;
diff --git a/src/Neo.Json/Neo.Json.csproj b/src/Neo.Json/Neo.Json.csproj
index 94943d4402..285d3f7cf4 100644
--- a/src/Neo.Json/Neo.Json.csproj
+++ b/src/Neo.Json/Neo.Json.csproj
@@ -3,6 +3,7 @@
enable
enable
+ preview
NEO;JSON
diff --git a/src/Neo.Json/Serialization/ArraySerializer.cs b/src/Neo.Json/Serialization/ArraySerializer.cs
new file mode 100644
index 0000000000..b90f44c280
--- /dev/null
+++ b/src/Neo.Json/Serialization/ArraySerializer.cs
@@ -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 : Serializer
+{
+ private static readonly Serializer elementSerializer = GetSerializer();
+
+ [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()
+ };
+}
diff --git a/src/Neo.Json/Serialization/BooleanSerializer.cs b/src/Neo.Json/Serialization/BooleanSerializer.cs
new file mode 100644
index 0000000000..67b139b935
--- /dev/null
+++ b/src/Neo.Json/Serialization/BooleanSerializer.cs
@@ -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
+{
+ protected internal override JBoolean Serialize(bool obj) => obj;
+
+ protected internal override bool Deserialize(JToken? json) => json!.GetBoolean();
+}
diff --git a/src/Neo.Json/Serialization/ByteArraySerializer.cs b/src/Neo.Json/Serialization/ByteArraySerializer.cs
new file mode 100644
index 0000000000..d0c471a8bb
--- /dev/null
+++ b/src/Neo.Json/Serialization/ByteArraySerializer.cs
@@ -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
+{
+ [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());
+}
diff --git a/src/Neo.Json/Serialization/CompositeSerializer.cs b/src/Neo.Json/Serialization/CompositeSerializer.cs
new file mode 100644
index 0000000000..8683e77f01
--- /dev/null
+++ b/src/Neo.Json/Serialization/CompositeSerializer.cs
@@ -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 : Serializer
+{
+ private static readonly (PropertyInfo, Serializer, Func, Action)[] 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>(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>(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();
+ }
+ }
+}
diff --git a/src/Neo.Json/Serialization/EnumSerializer.cs b/src/Neo.Json/Serialization/EnumSerializer.cs
new file mode 100644
index 0000000000..1754fa3a02
--- /dev/null
+++ b/src/Neo.Json/Serialization/EnumSerializer.cs
@@ -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 : Serializer where T : unmanaged, Enum
+{
+ protected internal override JString Serialize(T obj) => obj.ToString();
+
+ protected internal override T Deserialize(JToken? json) => json!.GetEnum();
+}
diff --git a/src/Neo.Json/Serialization/Int64Serializer.cs b/src/Neo.Json/Serialization/Int64Serializer.cs
new file mode 100644
index 0000000000..9eee863bf8
--- /dev/null
+++ b/src/Neo.Json/Serialization/Int64Serializer.cs
@@ -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
+{
+ 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()
+ };
+}
diff --git a/src/Neo.Json/Serialization/IntegerSerializer.cs b/src/Neo.Json/Serialization/IntegerSerializer.cs
new file mode 100644
index 0000000000..f3dd458b89
--- /dev/null
+++ b/src/Neo.Json/Serialization/IntegerSerializer.cs
@@ -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 : Serializer 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()
+ };
+}
diff --git a/src/Neo.Json/Serialization/Serializer.cs b/src/Neo.Json/Serialization/Serializer.cs
new file mode 100644
index 0000000000..9ac44c147c
--- /dev/null
+++ b/src/Neo.Json/Serialization/Serializer.cs
@@ -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 serializers = new()
+ {
+ [typeof(bool)] = new BooleanSerializer(),
+ [typeof(sbyte)] = new IntegerSerializer(),
+ [typeof(byte)] = new IntegerSerializer(),
+ [typeof(short)] = new IntegerSerializer(),
+ [typeof(ushort)] = new IntegerSerializer(),
+ [typeof(int)] = new IntegerSerializer(),
+ [typeof(uint)] = new IntegerSerializer(),
+ [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 GetSerializer()
+ {
+ return (Serializer)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 obj) where T : struct
+ {
+ Serializer serializer = GetSerializer();
+ return serializer.Serialize(obj);
+ }
+
+ [return: NotNullIfNotNull("json")]
+ public static T? Deserialize(JToken? json)
+ {
+ Serializer serializer = GetSerializer();
+ return serializer.Deserialize(json);
+ }
+
+ private protected abstract JToken SerializeObject(object obj);
+}
+
+public abstract class Serializer : 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);
+}
diff --git a/src/Neo.Json/Serialization/SerializerAttribute.cs b/src/Neo.Json/Serialization/SerializerAttribute.cs
new file mode 100644
index 0000000000..bc4c214a46
--- /dev/null
+++ b/src/Neo.Json/Serialization/SerializerAttribute.cs
@@ -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 : Attribute where T : Serializer
+{
+}
diff --git a/src/Neo.Json/Serialization/StringSerializer.cs b/src/Neo.Json/Serialization/StringSerializer.cs
new file mode 100644
index 0000000000..45db2d8a71
--- /dev/null
+++ b/src/Neo.Json/Serialization/StringSerializer.cs
@@ -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 StringSerializer : Serializer
+{
+ [return: NotNullIfNotNull("json")]
+ protected internal override JString? Serialize(string? obj) => obj;
+
+ [return: NotNullIfNotNull("json")]
+ protected internal override string? Deserialize(JToken? json) => json?.GetString();
+}
diff --git a/src/Neo.Json/Serialization/UInt64Serializer.cs b/src/Neo.Json/Serialization/UInt64Serializer.cs
new file mode 100644
index 0000000000..f0e197c26d
--- /dev/null
+++ b/src/Neo.Json/Serialization/UInt64Serializer.cs
@@ -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 UInt64Serializer : Serializer
+{
+ protected internal override JToken Serialize(ulong obj) => obj <= JNumber.MAX_SAFE_INTEGER ? obj : obj.ToString();
+
+ protected internal override ulong Deserialize(JToken? json) => json switch
+ {
+ JNumber n => (ulong)n.Value,
+ JString s => ulong.Parse(s.Value),
+ _ => throw new FormatException()
+ };
+}