From 6d4147777a5539338e045d9e4a424ee50eb78a74 Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 30 May 2022 12:35:40 +0200 Subject: [PATCH] Support System.Text.Json This starts adding support for System.Text.Json, which can be toggled by the existing NOJSONNET constant. --- NBitcoin.Tests/AltcoinTests.cs | 3 +- NBitcoin/NBitcoin.csproj | 15 ++- NBitcoin/RPC/FundRawTransactionOptions.cs | 9 +- NBitcoin/RPC/ImportMultiAddress.cs | 83 ++++++++++++++--- .../RPC/SignRawTransactionWithKeyRequest.cs | 7 +- .../MemberOptInJsonConverter.cs | 93 +++++++++++++++++++ 6 files changed, 179 insertions(+), 31 deletions(-) create mode 100644 NBitcoin/SystemJsonConverters/MemberOptInJsonConverter.cs diff --git a/NBitcoin.Tests/AltcoinTests.cs b/NBitcoin.Tests/AltcoinTests.cs index 54b29ca038..751904ecf9 100644 --- a/NBitcoin.Tests/AltcoinTests.cs +++ b/NBitcoin.Tests/AltcoinTests.cs @@ -1,6 +1,5 @@ using NBitcoin.Altcoins.Elements; using NBitcoin.RPC; -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.IO; @@ -8,7 +7,7 @@ using System.Net; using System.Threading.Tasks; using NBitcoin.Altcoins; -using NBitcoin.JsonConverters; +using Newtonsoft.Json.Linq; using Newtonsoft.Json; using Xunit; using Encoders = NBitcoin.DataEncoders.Encoders; diff --git a/NBitcoin/NBitcoin.csproj b/NBitcoin/NBitcoin.csproj index 7f869f60dc..c11c406056 100644 --- a/NBitcoin/NBitcoin.csproj +++ b/NBitcoin/NBitcoin.csproj @@ -52,16 +52,25 @@ $(DefineConstants);NO_RCA;NOPARALLEL;NETSTANDARD;NETSTANDARD1X;NULLABLE_SHIMS;NO_MEM_BUFFER;NOTRACESOURCE;NOCUSTOMSSLVALIDATION;NOSTRNORMALIZE;NOSOCKET;NOFILEIO;USEBC;NODEFAULTRNG;NODYNAMIC;NOX509;NONATIVEHASH;NO_ARRAY_FILL;NO_NATIVE_HMACSHA512;NO_THREAD;NO_NATIVERIPEMD160;NO_NATIVESHA1;NO_SOCKETASYNC - + $(DefineConstants);SECP256K1_VERIFY + NOJSONNET + true - - + + + + + + + + + diff --git a/NBitcoin/RPC/FundRawTransactionOptions.cs b/NBitcoin/RPC/FundRawTransactionOptions.cs index 02242fa41b..1e2795b74c 100644 --- a/NBitcoin/RPC/FundRawTransactionOptions.cs +++ b/NBitcoin/RPC/FundRawTransactionOptions.cs @@ -1,11 +1,4 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace NBitcoin.RPC +namespace NBitcoin.RPC { public class FundRawTransactionOptions { diff --git a/NBitcoin/RPC/ImportMultiAddress.cs b/NBitcoin/RPC/ImportMultiAddress.cs index 4518c3fd0c..e3600be0c6 100644 --- a/NBitcoin/RPC/ImportMultiAddress.cs +++ b/NBitcoin/RPC/ImportMultiAddress.cs @@ -1,13 +1,27 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; + using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +#if !NOJSONNET +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using NBitcoin.JsonConverters; +#else +using System.Text.Json; +using System.Text.Json.Serialization; +using NBitcoin.SystemJsonConverters; +#endif using NBitcoin.Scripting; namespace NBitcoin.RPC { - [JsonObject(MemberSerialization.OptIn)] +#if !NOJSONNET + [JsonElement(MemberSerialization.OptIn)] +#else +[JsonConverter(typeof(MemberOptInJsonConverter))] +#endif public class ImportMultiAddress { public class ScriptPubKeyObject @@ -27,7 +41,12 @@ public ScriptPubKeyObject(BitcoinAddress address) [JsonIgnore] public Script ScriptPubKey { get; set; } +#if !NOJSONNET [JsonProperty("address", NullValueHandling = NullValueHandling.Ignore)] +#else + [JsonPropertyName("address")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public BitcoinAddress Address { get; set; } /// @@ -43,39 +62,79 @@ public bool IsAddress } } } - +#if !NOJSONNET [JsonProperty("scriptPubKey", NullValueHandling = NullValueHandling.Ignore)] +#else + [JsonPropertyName("scriptPubKey")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif [JsonConverter(typeof(ImportMultiScriptPubKeyConverter))] public ScriptPubKeyObject ScriptPubKey { get; set; } /// /// Creation time of the key, keep null if this address has just been generated /// +#if !NOJSONNET [JsonProperty("timestamp")] +#else + [JsonPropertyName("timestamp")] +#endif public DateTimeOffset? Timestamp { get; set; } - +#if !NOJSONNET [JsonProperty("redeemscript", NullValueHandling = NullValueHandling.Ignore)] +#else + [JsonPropertyName("redeemscript")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public Script RedeemScript { get; set; } - +#if !NOJSONNET [JsonProperty("pubkeys", NullValueHandling = NullValueHandling.Ignore)] +#else + [JsonPropertyName("pubkeys")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public PubKey[] PubKeys { get; set; } - +#if !NOJSONNET [JsonProperty("keys", NullValueHandling = NullValueHandling.Ignore)] +#else + [JsonPropertyName("keys")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public BitcoinSecret[] Keys { get; set; } - +#if !NOJSONNET [JsonProperty("internal", NullValueHandling = NullValueHandling.Ignore)] +#else + [JsonPropertyName("internal")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public bool? Internal { get; set; } - +#if !NOJSONNET [JsonProperty("watchonly", NullValueHandling = NullValueHandling.Ignore)] +#else + [JsonPropertyName("watchonly")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public bool? WatchOnly { get; set; } - +#if !NOJSONNET [JsonProperty("label", NullValueHandling = NullValueHandling.Ignore)] +#else + [JsonPropertyName("label")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public string Label { get; set; } - +#if !NOJSONNET [JsonProperty("desc", NullValueHandling = NullValueHandling.Ignore)] +#else + [JsonPropertyName("desc")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public OutputDescriptor Desc { get; set; } - +#if !NOJSONNET [JsonProperty("range", NullValueHandling = NullValueHandling.Ignore)] +#else + [JsonPropertyName("range")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +#endif public int[] Ranges { get; set; } [JsonIgnore] diff --git a/NBitcoin/RPC/SignRawTransactionWithKeyRequest.cs b/NBitcoin/RPC/SignRawTransactionWithKeyRequest.cs index 67b5758359..91b669de46 100644 --- a/NBitcoin/RPC/SignRawTransactionWithKeyRequest.cs +++ b/NBitcoin/RPC/SignRawTransactionWithKeyRequest.cs @@ -1,9 +1,4 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Text; - -namespace NBitcoin.RPC +namespace NBitcoin.RPC { public class SignRawTransactionRequest { diff --git a/NBitcoin/SystemJsonConverters/MemberOptInJsonConverter.cs b/NBitcoin/SystemJsonConverters/MemberOptInJsonConverter.cs new file mode 100644 index 0000000000..6db7d5ad06 --- /dev/null +++ b/NBitcoin/SystemJsonConverters/MemberOptInJsonConverter.cs @@ -0,0 +1,93 @@ +using System; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace NBitcoin.SystemJsonConverters +{ + public class MemberOptInJsonConverter : JsonConverter where T : class + { + public override bool HandleNull => false; + + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return JsonSerializer.Deserialize(ref reader, options); + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + if (value is null) return; + writer.WriteStartObject(); + var type = value.GetType(); + var props = type.GetProperties(); + + foreach (var property in props) + { + var propValue = property.GetValue(value); + var jsonAttribute = property.GetCustomAttribute(); + if (jsonAttribute is null) + { + continue; + } + + var ignoreAttribute = property.GetCustomAttribute(); + if (ignoreAttribute is not null) + { + switch (ignoreAttribute.Condition) + { + case JsonIgnoreCondition.Never: + break; + case JsonIgnoreCondition.Always: + continue; + case JsonIgnoreCondition.WhenWritingDefault: + if (propValue == null) + { + continue; + } + + break; + case JsonIgnoreCondition.WhenWritingNull: + + if (propValue == default) + { + continue; + } + + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + switch (true) + { + case true when propValue is not null: + case true when propValue is null && options.DefaultIgnoreCondition == JsonIgnoreCondition.Never: + writer.WritePropertyName(property.Name); + JsonSerializer.Serialize(writer, propValue, options); + break; + } + } + + writer.WriteEndObject(); + } + } + + public class MemberOptInJsonConverter : JsonConverterFactory + { + public override bool CanConvert(Type typeToConvert) => !typeToConvert.IsPrimitive; + + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + JsonConverter converter = (JsonConverter) Activator.CreateInstance( + typeof(MemberOptInJsonConverter<>) + .MakeGenericType(new Type[] {typeToConvert}), + BindingFlags.Instance | BindingFlags.Public, + binder: null, + args: null, + culture: null)!; + + return converter; + } + } +}