Skip to content

Commit

Permalink
Merge pull request #2322 from dahlia/json-serializer
Browse files Browse the repository at this point in the history
Serialize/deserialize better with `System.Text.Json.JsonSerializer`
  • Loading branch information
dahlia authored Sep 20, 2022
2 parents 7ee45e8 + c51fd74 commit 1ab3727
Show file tree
Hide file tree
Showing 21 changed files with 608 additions and 31 deletions.
7 changes: 5 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ commands:
"Libplanet.Net.Tests.Protocols.ProtocolTest"
"Libplanet.Net.Tests.SwarmTest"
"Libplanet.Net.Tests.Transports.NetMQTransportTest"
"Libplanet.Node.Tests.SwarmConfigTest"
)
args=(
"--hang-seconds=60"
Expand All @@ -358,8 +359,10 @@ commands:
done
for project in *.Tests; do
if [[
$project != *"Explorer"*
&& $project != *"RocksDBStore"*
$project != Libplanet.Explorer.Tests
&& $project != Libplanet.Extensions.Cocona.Tests
&& $project != Libplanet.RocksDBStore.Tests
&& $project != Libplanet.Tools.Tests
]]
then
args+=("$PWD/$project/bin/Release/net47/$project.dll")
Expand Down
30 changes: 30 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ To be released.

### Backward-incompatible API changes

- (Libplanet.Extensions.Cocona) The return type of
`Utils.DeserializeHumanReadable<T>()` static method became `T?` (was `T`).
[[#2322]]

### Backward-incompatible network protocol changes

### Backward-incompatible storage format changes
Expand All @@ -18,12 +22,38 @@ To be released.

### Behavioral changes

- Many types became serialized and deserialized better with
[`System.Text.Json.JsonSerializer`] as they now have their own
[custom converters]. Note that these serializations are unavailable
on Unity due to its incomplete reflection support. [[#2322]]

- An `Address` became represented as a single hexadecimal string in JSON.
- A `BlockHash` became represented as a single hexadecimal string in JSON.
- A `Currency` became represented as an object with values in JSON.
Note that it contains its `Hash` and it throws `JsonException`
if a JSON object to deserialize has an inconsistent `Hash` with
other field values.
- A `FungibleAssetValue` became represented as an object with
its `Currency` object and `Quantity` string.
- A `HashDigest` became represented as a single hexadecimal string in
JSON.
- A `TxId` became represented as a single hexadecimal string in JSON.

### Bug fixes

### Dependencies

- Upgrade *System.Text.Json* from [4.7.2][System.Text.Json 4.7.2] to
[6.0.6][System.Text.Json 6.0.6]. [[#2322]]

### CLI tools

[#2322]: https://github.com/planetarium/libplanet/pull/2322
[`System.Text.Json.JsonSerializer`]: https://docs.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializer
[custom converters]: https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to
[System.Text.Json 4.7.2]: https://www.nuget.org/packages/System.Text.Json/4.7.2
[System.Text.Json 6.0.6]: https://www.nuget.org/packages/System.Text.Json/6.0.6


Version 0.42.0
--------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
</PropertyGroup>

<PropertyGroup>
<LangVersion>8.0</LangVersion>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>9.0</LangVersion>
<TargetFrameworks>netstandard2.0;netstandard2.1;netcoreapp3.1</TargetFrameworks>
<RootNamespace>Libplanet.Extensions.Cocona</RootNamespace>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Nullable>enable</Nullable>
Expand Down Expand Up @@ -40,7 +40,7 @@
runtime; build; native; contentfiles; analyzers
</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="4.7.2" />
<PackageReference Include="System.Text.Json" Version="6.0.6" />
</ItemGroup>

<ItemGroup Condition="'$(SkipSonar)' != 'true'">
Expand Down
25 changes: 15 additions & 10 deletions Libplanet.Extensions.Cocona/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,15 +147,19 @@ public static string SerializeHumanReadable<T>(T target) =>
}
);

public static T DeserializeHumanReadable<T>(string target) => JsonSerializer.Deserialize<T>(
target, new JsonSerializerOptions
{
Converters =
public static T? DeserializeHumanReadable<T>(string target)
where T : notnull
=>
JsonSerializer.Deserialize<T>(
target, new JsonSerializerOptions
{
new ByteArrayStringJsonConverter(),
new DateTimeOffsetJsonConverter(),
},
});
Converters =
{
new ByteArrayStringJsonConverter(),
new DateTimeOffsetJsonConverter(),
},
}
);

private static void PrintTable(object[] header, IEnumerable<object[]> rows)
{
Expand Down Expand Up @@ -239,7 +243,7 @@ public override ImmutableArray<byte> Read(
Type typeToConvert,
JsonSerializerOptions options)
{
var hexString = reader.GetString();
var hexString = reader.GetString() ?? throw new JsonException("Expected a string.");
return ImmutableArray.Create(ByteUtil.ParseHex(hexString));
}

Expand All @@ -257,7 +261,8 @@ public override DateTimeOffset Read(
Type typeToConvert,
JsonSerializerOptions options)
{
var jsonString = reader.GetString();
var jsonString = reader.GetString()
?? throw new JsonException("Expected a string.");
return DateTimeOffset.ParseExact(
jsonString,
DateTimeOffsetFormat,
Expand Down
2 changes: 1 addition & 1 deletion Libplanet.Node.Tests/Libplanet.Node.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<PackageReference Include="Menees.Analyzers.2017" Version="2.0.3">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.TestCorrelator" Version="3.2.0" />
Expand Down
17 changes: 15 additions & 2 deletions Libplanet.Node/SwarmConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,20 @@ public class SwarmConfig
/// <param name="jsonString">The json <see cref="string"/> to parse.</param>
/// <returns>The <see cref="SwarmConfig"/> instance parsed from
/// <paramref name="jsonString"/>.</returns>
/// <exception cref="JsonException">Thrown when the input <paramref name="jsonString"/>
/// is invalid.</exception>
public static SwarmConfig FromJson(string jsonString)
{
return JsonSerializer.Deserialize<SwarmConfig>(jsonString, _jsonSerializerOptions);
SwarmConfig? config =
JsonSerializer.Deserialize<SwarmConfig>(jsonString, _jsonSerializerOptions);
if (config is { } cfg)
{
return cfg;
}

// JsonSerializer.Deserialize() can return null if the root JSON node is null.
// https://github.com/dotnet/runtime/discussions/60195#discussioncomment-1649740
throw new JsonException("A null is disallowed for the root of the input JSON tree.");
}

/// <summary>
Expand Down Expand Up @@ -134,7 +145,9 @@ public override BoundPeer Read(
Type typeToConvert,
JsonSerializerOptions options)
{
return BoundPeer.ParsePeer(reader.GetString());
return BoundPeer.ParsePeer(
reader.GetString() ?? throw new JsonException("Expected a string.")
);
}

public override void Write(
Expand Down
15 changes: 13 additions & 2 deletions Libplanet.Tests/AddressTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Bencodex.Types;
using Libplanet.Crypto;
using Xunit;
using static Libplanet.Tests.TestUtils;

namespace Libplanet.Tests
{
Expand Down Expand Up @@ -133,7 +134,7 @@ public void AddressMustBe20Bytes()
continue;
}

byte[] addressBytes = TestUtils.GetRandomBytes(size);
byte[] addressBytes = GetRandomBytes(size);
Assert.Throws<ArgumentException>(() =>
new Address(addressBytes)
);
Expand Down Expand Up @@ -162,7 +163,7 @@ public void ConstructWithBinary()
[Fact]
public void ToByteArray()
{
byte[] addressBytes = TestUtils.GetRandomBytes(20);
byte[] addressBytes = GetRandomBytes(20);
var address = new Address(addressBytes);
Assert.Equal(addressBytes, address.ToByteArray());
}
Expand Down Expand Up @@ -327,5 +328,15 @@ public void ReplaceHexUpperCasePrefixString()
new Address("0X0123456789ABcdefABcdEfABcdEFabcDEFabCDEF")
);
}

[SkippableFact]
public void JsonSerialization()
{
var address = new Address("0123456789ABcdefABcdEfABcdEFabcDEFabCDEF");
AssertJsonSerializable(
address,
"\"0123456789ABcdefABcdEfABcdEFabcDEFabCDEF\""
);
}
}
}
34 changes: 34 additions & 0 deletions Libplanet.Tests/Assets/CurrencyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,5 +255,39 @@ public void Serialize()

Assert.Equal(bar, new Currency(bar.Serialize()));
}

[SkippableFact]
public void JsonSerialization()
{
#pragma warning disable CS0618 // must test obsoleted Currency.Legacy() for backwards compatibility
var foo = Currency.Legacy("FOO", 2, null);
#pragma warning restore CS0618 // must test obsoleted Currency.Legacy() for backwards compatibility
AssertJsonSerializable(foo, @"
{
""hash"": ""8db87f973776e2218113202e00e09e185fff8971"",
""ticker"": ""FOO"",
""decimalPlaces"": 2,
""minters"": null,
""maximumSupply"": null,
""totalSupplyTrackable"": false,
}
");

var bar =
Currency.Capped("BAR", 0, (100, 0), ImmutableHashSet.Create(AddressA, AddressB));
AssertJsonSerializable(bar, @"
{
""hash"": ""e4ee30562819a9e74be40098c76f84209d05da5e"",
""ticker"": ""BAR"",
""decimalPlaces"": 0,
""minters"": [
""5003712B63baAB98094aD678EA2B24BcE445D076"",
""D6D639DA5a58A78A564C2cD3DB55FA7CeBE244A9"",
],
""maximumSupply"": ""100.0"",
""totalSupplyTrackable"": true,
}
");
}
}
}
35 changes: 35 additions & 0 deletions Libplanet.Tests/Assets/FungibleAssetValueTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Runtime.Serialization.Formatters.Binary;
using Libplanet.Assets;
using Xunit;
using static Libplanet.Tests.TestUtils;

namespace Libplanet.Tests.Assets
{
Expand Down Expand Up @@ -531,6 +532,40 @@ public void Parse()
Assert.Equal(new FungibleAssetValue(FOO, 12, 0), FungibleAssetValue.Parse(FOO, "+12"));
Assert.Equal(new FungibleAssetValue(FOO, -12, 0), FungibleAssetValue.Parse(FOO, "-12"));
}

[SkippableFact]
public void JsonSerialization()
{
var v = new FungibleAssetValue(FOO, 123, 45);
AssertJsonSerializable(v, @"
{
""quantity"": ""123.45"",
""currency"": {
""hash"": ""946ea39b6f49926c0ed3df2a3aa0d2aba0f0fc25"",
""ticker"": ""FOO"",
""decimalPlaces"": 2,
""minters"": null,
""maximumSupply"": null,
""totalSupplyTrackable"": true,
}
}
");

v = new FungibleAssetValue(FOO, -456, 0);
AssertJsonSerializable(v, @"
{
""quantity"": ""-456"",
""currency"": {
""hash"": ""946ea39b6f49926c0ed3df2a3aa0d2aba0f0fc25"",
""ticker"": ""FOO"",
""decimalPlaces"": 2,
""minters"": null,
""maximumSupply"": null,
""totalSupplyTrackable"": true,
}
}
");
}
}
}

Expand Down
20 changes: 16 additions & 4 deletions Libplanet.Tests/Blocks/BlockHashTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Security.Cryptography;
using Libplanet.Blocks;
using Xunit;
using static Libplanet.Tests.TestUtils;

namespace Libplanet.Tests.Blocks
{
Expand All @@ -14,8 +15,8 @@ public class BlockHashTest
public void DefaultConstructor()
{
BlockHash def = default;
TestUtils.AssertBytesEqual(new byte[32].ToImmutableArray(), def.ByteArray);
TestUtils.AssertBytesEqual(new byte[32], def.ToByteArray());
AssertBytesEqual(new byte[32].ToImmutableArray(), def.ByteArray);
AssertBytesEqual(new byte[32], def.ToByteArray());
}

[Fact]
Expand Down Expand Up @@ -88,12 +89,12 @@ public void FromHashDigest()
public void DeriveFrom()
{
byte[] foo = { 0x66, 0x6f, 0x6f }, bar = { 0x62, 0x61, 0x72 };
TestUtils.AssertBytesEqual(
AssertBytesEqual(
BlockHash.FromString(
"2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae"),
BlockHash.DeriveFrom(foo)
);
TestUtils.AssertBytesEqual(
AssertBytesEqual(
BlockHash.FromString(
"fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9"),
BlockHash.DeriveFrom(bar)
Expand Down Expand Up @@ -138,5 +139,16 @@ public void SerializeAndDeserialize()

Assert.Equal(deserialized, expected);
}

[SkippableFact]
public void JsonSerialization()
{
BlockHash hash = BlockHash.FromString(
"2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae");
AssertJsonSerializable(
hash,
"\"2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae\""
);
}
}
}
12 changes: 12 additions & 0 deletions Libplanet.Tests/HashDigestTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using Xunit;
using static Libplanet.Tests.TestUtils;

namespace Libplanet.Tests
{
Expand Down Expand Up @@ -173,5 +174,16 @@ public void SerializeAndDeserializeSHA256()

Assert.Equal(deserializedHashDigest, expectedHashDigest);
}

[SkippableFact]
public void JsonSerialization()
{
HashDigest<SHA1> digest =
HashDigest<SHA1>.FromString("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33");
AssertJsonSerializable(
digest,
"\"0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33\""
);
}
}
}
Loading

0 comments on commit 1ab3727

Please sign in to comment.