diff --git a/Substrate.NetApi.TestNode/ExtrinsicsTest.cs b/Substrate.NetApi.TestNode/ExtrinsicsTest.cs
index 5133a15..f3cd67a 100644
--- a/Substrate.NetApi.TestNode/ExtrinsicsTest.cs
+++ b/Substrate.NetApi.TestNode/ExtrinsicsTest.cs
@@ -1,15 +1,13 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
+using NUnit.Framework;
+using NUnit.Framework.Internal;
+using Schnorrkel.Keys;
using Substrate.NetApi.Model.Extrinsics;
using Substrate.NetApi.Model.Rpc;
using Substrate.NetApi.Model.Types;
using Substrate.NetApi.Model.Types.Base;
-using Substrate.NetApi.Model.Types.Primitive;
-using NUnit.Framework;
-using Schnorrkel.Keys;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
namespace Substrate.NetApi.TestNode
{
@@ -17,7 +15,7 @@ public class ExtrinsicsTest
{
public MiniSecret MiniSecretAlice => new MiniSecret(Utils.HexToByteArray("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a"), ExpandMode.Ed25519);
public Account Alice => Account.Build(KeyType.Sr25519, MiniSecretAlice.ExpandToSecret().ToBytes(), MiniSecretAlice.GetPair().Public.Key);
-
+
public MiniSecret MiniSecretBob => new MiniSecret(Utils.HexToByteArray("0x398f0c28f98885e046333d4a41c19cee4c37368a9832c6502f6cfd182e2aef89"), ExpandMode.Ed25519);
public Account Bob => Account.Build(KeyType.Sr25519, MiniSecretBob.ExpandToSecret().ToBytes(), MiniSecretBob.GetPair().Public.Key);
@@ -67,119 +65,89 @@ public async Task TearDownAsync()
}
///
- /// Extrinsic Remark test
+ /// Extrinsic Submit And Watch
///
///
[Test]
- public async Task Extrinsic_RemarkTestAsync()
+ public async Task Extrinsic_SubmitAndWatchExtrinsicAsync()
{
var method = new Method(0, "System", 0, "remark", new byte[] { 0x04, 0xFF });
var taskCompletionSource = new TaskCompletionSource<(bool, Hash)>();
- await _substrateClient.Author.SubmitAndWatchExtrinsicAsync((string subscriptionId, ExtrinsicStatus extrinsicUpdate) => Callback(subscriptionId, extrinsicUpdate, taskCompletionSource), method, Alice, _chargeType, 64, CancellationToken.None);
-
- var finished = await Task.WhenAny(taskCompletionSource.Task, Task.Delay(TimeSpan.FromMinutes(1))); // 5 minutes or any appropriate timeout
+ await _substrateClient.Author.SubmitAndWatchExtrinsicAsync((string subscriptionId, ExtrinsicStatus extrinsicUpdate) =>
+ {
+ if (extrinsicUpdate.ExtrinsicState == ExtrinsicState.Finalized ||
+ extrinsicUpdate.ExtrinsicState == ExtrinsicState.Dropped ||
+ extrinsicUpdate.ExtrinsicState == ExtrinsicState.Invalid)
+ {
+ taskCompletionSource.SetResult((true, extrinsicUpdate.Hash));
+ }
+ }, method, Alice, _chargeType, 64, CancellationToken.None);
+
+ var finished = await Task.WhenAny(taskCompletionSource.Task, Task.Delay(TimeSpan.FromMinutes(1)));
Assert.AreEqual(taskCompletionSource.Task, finished, "Test timed out waiting for final callback");
}
+ ///
+ /// Transaction Unstable Submit And Watch
+ ///
+ ///
[Test]
- public async Task Extrinsic_FailTestAsync()
+ public async Task Extrinsic_TransactionUnstableSubmitAndWatchAsync()
{
-
- var method = new Method(0, "System", 1, "set_heap_pages", new U64(999).Encode());
+ var method = new Method(0, "System", 0, "remark", new byte[] { 0x04, 0xFF });
var taskCompletionSource = new TaskCompletionSource<(bool, Hash)>();
-
- var account = Bob;
-
- var subscriptionId = await _substrateClient.Author.SubmitAndWatchExtrinsicAsync((string subscriptionId, ExtrinsicStatus extrinsicUpdate) => Callback(subscriptionId, extrinsicUpdate, taskCompletionSource), method, account, _chargeType, 64, CancellationToken.None);
-
- var finished = await Task.WhenAny(taskCompletionSource.Task, Task.Delay(TimeSpan.FromMinutes(1))); // 5 minutes or any appropriate timeout
- Assert.AreEqual(taskCompletionSource.Task, finished, "Test timed out waiting for final callback");
-
- var block = await _substrateClient.Chain.GetBlockAsync((await taskCompletionSource.Task).Item2);
-
- var accountExtrinsics = block.Block.Extrinsics.Where(p => p.Signed && p.Account.Value == account.Value);
- Assert.AreEqual(1, accountExtrinsics.Count());
-
- }
-
- ///
- /// Extrinsic Transfer Callback test
- ///
- ///
- ///
- ///
- private static void Callback(string subscriptionId, ExtrinsicStatus extrinsicUpdate, TaskCompletionSource<(bool, Hash)> taskCompletionSource)
- {
- ActionExtrinsicUpdate(subscriptionId, extrinsicUpdate);
- if (extrinsicUpdate.ExtrinsicState == ExtrinsicState.Finalized ||
- extrinsicUpdate.ExtrinsicState == ExtrinsicState.Dropped ||
- extrinsicUpdate.ExtrinsicState == ExtrinsicState.Invalid)
+ _ = await _substrateClient.Unstable.TransactionUnstableSubmitAndWatchAsync((string subscriptionId, TransactionEventInfo extrinsicUpdate) =>
{
- taskCompletionSource.SetResult((true, extrinsicUpdate.Hash));
- }
+ if (extrinsicUpdate.TransactionEvent == TransactionEvent.Finalized ||
+ extrinsicUpdate.TransactionEvent == TransactionEvent.Dropped ||
+ extrinsicUpdate.TransactionEvent == TransactionEvent.Invalid ||
+ extrinsicUpdate.TransactionEvent == TransactionEvent.Error)
+ {
+ taskCompletionSource.SetResult((true, extrinsicUpdate.Hash));
+ }
+ }, method, Alice, _chargeType, 64, CancellationToken.None);
+
+ var finished = await Task.WhenAny(taskCompletionSource.Task, Task.Delay(TimeSpan.FromMinutes(1)));
+ Assert.AreEqual(taskCompletionSource.Task, finished, "Test timed out waiting for final callback");
}
///
- /// Simple extrinsic tester
+ /// Transaction Unstable Unwatch
///
- ///
- ///
- private static void ActionExtrinsicUpdate(string subscriptionId, ExtrinsicStatus extrinsicUpdate)
+ ///
+ [Test, Timeout(10000)] // Timeout after 10 seconds
+ public async Task Extrinsic_TransactionUnstableUnwatchAsync()
{
- if (subscriptionId == null || subscriptionId.Length == 0)
+ var method = new Method(0, "System", 0, "remark", new byte[] { 0x04, 0xFF });
+ var cancellationTokenSource = new CancellationTokenSource();
+
+ var taskCompletionSource = new TaskCompletionSource();
+ var subscriptionId = await _substrateClient.Unstable.TransactionUnstableSubmitAndWatchAsync(
+ (subscriptionId, extrinsicUpdate) =>
+ {
+ {
+ if (extrinsicUpdate.TransactionEvent != TransactionEvent.Validated)
+ {
+ taskCompletionSource.SetResult(true);
+ }
+ }
+ },
+ method, Alice, _chargeType, 64, cancellationTokenSource.Token);
+
+ var unsubscribed = await _substrateClient.Unstable.TransactionUnstableUnwatchAsync(subscriptionId);
+ Assert.IsTrue(unsubscribed, "Unsubscribing from transaction should be successful.");
+
+ // Optionally: wait for the callback to be called, which should not happen
+ var callbackCalled = await Task.WhenAny(taskCompletionSource.Task, Task.Delay(500));
+ if (callbackCalled == taskCompletionSource.Task)
{
- Assert.IsTrue(false);
+ Assert.Fail("Callback should not be called after unsubscribing.");
}
- switch (extrinsicUpdate.ExtrinsicState)
- {
- case ExtrinsicState.Future:
- Assert.IsTrue(false);
- break;
-
- case ExtrinsicState.Ready:
- Assert.IsTrue(true);
- break;
-
- case ExtrinsicState.Dropped:
- Assert.IsTrue(false);
- break;
-
- case ExtrinsicState.Invalid:
- Assert.IsTrue(false);
- break;
-
- case ExtrinsicState.Broadcast:
- Assert.IsTrue(extrinsicUpdate.Broadcast != null);
- break;
-
- case ExtrinsicState.InBlock:
- Assert.IsTrue(extrinsicUpdate.Hash.Value.Length > 0);
- break;
-
- case ExtrinsicState.Retracted:
- Assert.IsTrue(extrinsicUpdate.Hash.Value.Length > 0);
- break;
-
- case ExtrinsicState.FinalityTimeout:
- Assert.IsTrue(extrinsicUpdate.Hash.Value.Length > 0);
- break;
-
- case ExtrinsicState.Finalized:
- Assert.IsTrue(extrinsicUpdate.Hash.Value.Length > 0);
- break;
-
- case ExtrinsicState.Usurped:
- Assert.IsTrue(extrinsicUpdate.Hash.Value.Length > 0);
- break;
-
- default:
- Assert.IsTrue(false);
- break;
-
- }
+ // Cleanup if needed
+ cancellationTokenSource.Cancel();
}
}
}
\ No newline at end of file
diff --git a/Substrate.NetApi/Model/Rpc/ExtrinsicStatus.cs b/Substrate.NetApi/Model/Rpc/ExtrinsicStatus.cs
index 8a6b590..4db6061 100644
--- a/Substrate.NetApi/Model/Rpc/ExtrinsicStatus.cs
+++ b/Substrate.NetApi/Model/Rpc/ExtrinsicStatus.cs
@@ -1,5 +1,6 @@
using Newtonsoft.Json;
using Substrate.NetApi.Model.Types.Base;
+using System;
namespace Substrate.NetApi.Model.Rpc
{
diff --git a/Substrate.NetApi/Model/Rpc/TransactionEventInfo.cs b/Substrate.NetApi/Model/Rpc/TransactionEventInfo.cs
new file mode 100644
index 0000000..5e9c891
--- /dev/null
+++ b/Substrate.NetApi/Model/Rpc/TransactionEventInfo.cs
@@ -0,0 +1,36 @@
+using Substrate.NetApi.Model.Types.Base;
+
+namespace Substrate.NetApi.Model.Rpc
+{
+ public enum TransactionEvent
+ {
+ Validated,
+
+ Broadcasted,
+
+ BestChainBlockIncluded,
+
+ Finalized,
+
+ Error,
+
+ Invalid,
+
+ Dropped
+ }
+
+ public sealed class TransactionEventInfo
+ {
+ public TransactionEvent TransactionEvent { get; set; }
+
+ public uint? NumPeers { get; set; }
+
+ public Hash Hash { get; set; }
+
+ public uint? Index { get; set; }
+
+ public bool? Broadcasted { get; set; }
+
+ public string Error { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/Substrate.NetApi/Modules/Contracts/IUnstableCalls.cs b/Substrate.NetApi/Modules/Contracts/IUnstableCalls.cs
new file mode 100644
index 0000000..6e51f2d
--- /dev/null
+++ b/Substrate.NetApi/Modules/Contracts/IUnstableCalls.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Substrate.NetApi.Model.Extrinsics;
+using Substrate.NetApi.Model.Rpc;
+using Substrate.NetApi.Model.Types;
+using Substrate.NetApi.Model.Types.Base;
+
+namespace Substrate.NetApi.Modules.Contracts
+{
+ public interface IUnstableCalls
+ {
+ ///
+ /// Submit and subscribe to watch an extrinsic until unsubscribed
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task TransactionUnstableSubmitAndWatchAsync(Action callback, Method method, Account account, ChargeType charge, uint lifeTime);
+
+ ///
+ /// Submit and subscribe to watch an extrinsic until unsubscribed
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task TransactionUnstableSubmitAndWatchAsync(Action callback, Method method, Account account, ChargeType charge, uint lifeTime, CancellationToken token);
+
+ ///
+ /// Submit and subscribe to watch an extrinsic until unsubscribed
+ ///
+ ///
+ ///
+ ///
+ Task TransactionUnstableSubmitAndWatchAsync(Action callback, string parameters);
+
+ ///
+ /// Submit and subscribe to watch an extrinsic until unsubscribed
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task TransactionUnstableSubmitAndWatchAsync(Action callback, string parameters, CancellationToken token);
+
+ ///
+ /// Unsuscribe to given subscription id
+ ///
+ ///
+ ///
+ Task TransactionUnstableUnwatchAsync(string subscriptionId);
+
+ ///
+ /// Unsuscribe to given subscription id
+ ///
+ ///
+ ///
+ ///
+ Task TransactionUnstableUnwatchAsync(string subscriptionId, CancellationToken token);
+ }
+}
\ No newline at end of file
diff --git a/Substrate.NetApi/Modules/UnstableCalls.cs b/Substrate.NetApi/Modules/UnstableCalls.cs
new file mode 100644
index 0000000..421896c
--- /dev/null
+++ b/Substrate.NetApi/Modules/UnstableCalls.cs
@@ -0,0 +1,111 @@
+using Substrate.NetApi.Model.Extrinsics;
+using Substrate.NetApi.Model.Rpc;
+using Substrate.NetApi.Model.Types;
+using Substrate.NetApi.Modules.Contracts;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Substrate.NetApi.Modules
+{
+ ///
+ /// New Api 2
+ ///
+ public class UnstableCalls : IUnstableCalls
+ {
+ /// The client
+ private readonly SubstrateClient _client;
+
+ ///
+ /// New Api 2
+ ///
+ ///
+ internal UnstableCalls(SubstrateClient client)
+ {
+ _client = client;
+ }
+
+ ///
+ /// Transaction Unstable Submit And Watch Async
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task TransactionUnstableSubmitAndWatchAsync(Action callback, Method method, Account account, ChargeType charge, uint lifeTime)
+ {
+ var extrinsic = await _client.GetExtrinsicParametersAsync(method, account, charge, lifeTime, signed: true, CancellationToken.None);
+
+ return await TransactionUnstableSubmitAndWatchAsync(callback, Utils.Bytes2HexString(extrinsic.Encode()));
+ }
+
+ ///
+ /// Transaction Unstable Submit And Watch Async
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task TransactionUnstableSubmitAndWatchAsync(Action callback, Method method, Account account, ChargeType charge, uint lifeTime, CancellationToken token)
+ {
+ var extrinsic = await _client.GetExtrinsicParametersAsync(method, account, charge, lifeTime, signed: true, token);
+ var extrinsicHex = Utils.Bytes2HexString(extrinsic.Encode());
+ return await TransactionUnstableSubmitAndWatchAsync(callback, extrinsicHex);
+ }
+
+ ///
+ /// Transaction Unstable Submit And Watch Async
+ ///
+ ///
+ ///
+ ///
+ public async Task TransactionUnstableSubmitAndWatchAsync(Action callback, string parameters)
+ {
+ return await TransactionUnstableSubmitAndWatchAsync(callback, parameters, CancellationToken.None);
+ }
+
+ ///
+ /// Transaction Unstable Submit And Watch Async
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task TransactionUnstableSubmitAndWatchAsync(Action callback, string parameters, CancellationToken token)
+ {
+ var subscriptionId =
+ await _client.InvokeAsync("transaction_unstable_submitAndWatch", new object[] { parameters }, token);
+ _client.Listener.RegisterCallBackHandler(subscriptionId, callback);
+ return subscriptionId;
+ }
+
+ ///
+ /// Transaction Unstable Unwatch Async
+ ///
+ ///
+ ///
+ public async Task TransactionUnstableUnwatchAsync(string subscriptionId)
+ {
+ return await TransactionUnstableUnwatchAsync(subscriptionId, CancellationToken.None);
+ }
+
+ ///
+ /// Transaction Unstable Unwatch Async
+ ///
+ ///
+ ///
+ ///
+ public async Task TransactionUnstableUnwatchAsync(string subscriptionId, CancellationToken token)
+ {
+ var result =
+ await _client.InvokeAsync("transaction_unstable_unwatch", new object[] { subscriptionId }, token);
+ if (result) _client.Listener.UnregisterHeaderHandler(subscriptionId);
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Substrate.NetApi/SubscriptionListener.cs b/Substrate.NetApi/SubscriptionListener.cs
index cbd998c..adeb198 100644
--- a/Substrate.NetApi/SubscriptionListener.cs
+++ b/Substrate.NetApi/SubscriptionListener.cs
@@ -142,5 +142,11 @@ public void AuthorSubmitAndWatchExtrinsic(string subscription, ExtrinsicStatus r
{
GenericCallBack(subscription, result);
}
+
+ [JsonRpcMethod("transaction_unstable_submitExtrinsic")]
+ public void TransactionUnstableSubmitExtrinsic(string subscription, TransactionEventInfo result)
+ {
+ GenericCallBack(subscription, result);
+ }
}
}
\ No newline at end of file
diff --git a/Substrate.NetApi/Substrate.NetApi.csproj b/Substrate.NetApi/Substrate.NetApi.csproj
index a65d251..40fb86e 100644
--- a/Substrate.NetApi/Substrate.NetApi.csproj
+++ b/Substrate.NetApi/Substrate.NetApi.csproj
@@ -3,7 +3,7 @@
Substrate.NET.API
netstandard2.0;netstandard2.1;net6.0
- 0.9.10
+ 0.9.11
Substrate Gaming
Substrate Gaming
true
diff --git a/Substrate.NetApi/SubstrateClient.cs b/Substrate.NetApi/SubstrateClient.cs
index 71edbfa..bdb1fd0 100644
--- a/Substrate.NetApi/SubstrateClient.cs
+++ b/Substrate.NetApi/SubstrateClient.cs
@@ -36,6 +36,8 @@ public class SubstrateClient : IDisposable
private readonly ExtrinsicStatusJsonConverter _extrinsicStatusJsonConverter;
+ private readonly TransactionEventJsonConverter _transactionEventJsonConverter;
+
/// The request token sources.
private readonly ConcurrentDictionary _requestTokenSourceDict;
@@ -66,12 +68,14 @@ public SubstrateClient(Uri uri, ChargeType chargeType, bool bypassRemoteCertific
_extrinsicJsonConverter = new ExtrinsicJsonConverter(chargeType);
_extrinsicStatusJsonConverter = new ExtrinsicStatusJsonConverter();
+ _transactionEventJsonConverter = new TransactionEventJsonConverter();
System = new Modules.System(this);
Chain = new Chain(this);
Payment = new Payment(this);
State = new State(this);
Author = new Author(this);
+ Unstable = new UnstableCalls(this);
_requestTokenSourceDict = new ConcurrentDictionary();
}
@@ -108,6 +112,11 @@ public SubstrateClient(Uri uri, ChargeType chargeType, bool bypassRemoteCertific
/// The author.
public Author Author { get; }
+ ///
+ /// New Api 2
+ ///
+ public UnstableCalls Unstable { get; }
+
public SubscriptionListener Listener { get; } = new SubscriptionListener();
/// Gets a value indicating whether this object is connected.
@@ -195,6 +204,7 @@ public async Task ConnectAsync(bool useMetaData, bool standardSubstrate, Cancell
formatter.JsonSerializer.Converters.Add(new GenericTypeConverter());
formatter.JsonSerializer.Converters.Add(_extrinsicJsonConverter);
formatter.JsonSerializer.Converters.Add(_extrinsicStatusJsonConverter);
+ formatter.JsonSerializer.Converters.Add(_transactionEventJsonConverter);
_jsonRpc = new JsonRpc(new WebSocketMessageHandler(_socket, formatter));
_jsonRpc.TraceSource.Listeners.Add(new SerilogTraceListener.SerilogTraceListener());
diff --git a/Substrate.NetApi/TypeConverters/ExtrinsicStatusJsonConverter.cs b/Substrate.NetApi/TypeConverters/ExtrinsicStatusJsonConverter.cs
index b365c03..71ec3c3 100644
--- a/Substrate.NetApi/TypeConverters/ExtrinsicStatusJsonConverter.cs
+++ b/Substrate.NetApi/TypeConverters/ExtrinsicStatusJsonConverter.cs
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
using Substrate.NetApi.Model.Rpc;
using Substrate.NetApi.Model.Types.Base;
using System;
@@ -6,6 +7,92 @@
namespace Substrate.NetApi.TypeConverters
{
+ public class TransactionEventJsonConverter : JsonConverter
+ {
+ /// Reads the JSON representation of the object.
+ /// The to read from.
+ /// Type of the object.
+ /// The existing value of object being read. If there is no existing value then null will be used.
+ /// The existing value has a value.
+ /// The calling serializer.
+ /// The object value.
+ ///
+ /// Unimplemented {reader.TokenType} of type '{reader.ValueType}' and value '{reader.Value}'.
+ /// or
+ /// Unimplemented {reader.TokenType} of type '{reader.ValueType}' and value '{reader.Value}'.
+ ///
+ public override TransactionEventInfo ReadJson(JsonReader reader, Type objectType, TransactionEventInfo existingValue, bool hasExistingValue, JsonSerializer serializer)
+ {
+ var transactionEventStatus = hasExistingValue ? existingValue : new TransactionEventInfo();
+
+ var jObject = JObject.Load(reader);
+
+ var eventName = jObject["event"]?.ToString();
+ if (Enum.TryParse(eventName, true, out TransactionEvent transactionEvent))
+ {
+ transactionEventStatus.TransactionEvent = transactionEvent;
+
+ switch (transactionEvent)
+ {
+ case TransactionEvent.Validated:
+ break;
+
+ case TransactionEvent.Broadcasted:
+ transactionEventStatus.NumPeers = uint.Parse(jObject["numPeers"].ToString());
+ break;
+
+ case TransactionEvent.BestChainBlockIncluded:
+ var bestChainBlock = jObject["block"];
+ if (bestChainBlock != null)
+ {
+ transactionEventStatus.Hash = new Hash(jObject["block"]["hash"].ToString());
+ transactionEventStatus.Index = uint.Parse(jObject["block"]["index"].ToString());
+ }
+ break;
+
+ case TransactionEvent.Finalized:
+ transactionEventStatus.Hash = new Hash(jObject["block"]["hash"].ToString());
+ transactionEventStatus.Index = uint.Parse(jObject["block"]["index"].ToString());
+ break;
+
+ case TransactionEvent.Error:
+ transactionEventStatus.Error = jObject["error"].ToString();
+ break;
+
+ case TransactionEvent.Invalid:
+ transactionEventStatus.Error = jObject["error"].ToString();
+ break;
+
+ case TransactionEvent.Dropped:
+ // TODO, check if this works broadcassted boolean
+ //transactionEventStatus.Broadcasted = bool.Parse(jObject["broadcasted"].ToString());
+ transactionEventStatus.Error = jObject["error"].ToString();
+ break;
+
+ default:
+ throw new NotImplementedException(
+ $"Unimplemented state {transactionEvent} with value '{reader.Value}'.");
+
+
+ }
+ }
+
+ return transactionEventStatus;
+ }
+
+ ///
+ /// Writes the JSON representation of the object.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public override void WriteJson(JsonWriter writer, TransactionEventInfo value, JsonSerializer serializer)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
public class ExtrinsicStatusJsonConverter : JsonConverter
{
/// Reads the JSON representation of the object.