Why
.NET Core 3.0 came with a new built-in serializer, which should be a bit faster than Newtonsoft.JSON, and a bit better integrated. This will be the default JSON serializer/deserializer for ASP.NET Core going forward.
How
Getting it to work is pretty straightforward. It's a matter of passing it a stream and some options. The tricky part is ISO-8601 datetimes, you'll have to write a custom converter to handle those. See bellow how to do that.
The server
const http = require("http");
http
.createServer(function (req, res) {
console.log(req.url);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(`{"version": 42, "modifiedDate": "${new Date().toISOString()}" }`);
})
.listen(3030);
Calling the server will return a JSON with a version and a datetime string.
> curl http://localhost:3030/health
{"version": 42, "modifiedDate": "2020-02-23T16:27:57.046Z" }
The client.
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
namespace Api.Client
{
class Program
{
static async Task Main(string[] args)
{
var api = new Client("http://127.0.0.1:3030");
var healthResult = await api.GetHealth();
Console.WriteLine(healthResult.Version);
}
}
class Client
{
static JsonSerializerOptions JsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase
};
readonly string targetBase;
readonly HttpClient httpClient;
public Client(string targetBase)
{
this.targetBase = targetBase;
this.httpClient = new HttpClient();
}
private async Task<T> GetAsync<T>(string path)
{
var response = await this.httpClient.GetAsync($"{this.targetBase}/{path}");
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
return await JsonSerializer.DeserializeAsync<T>(responseStream, JsonOptions);
}
}
public Task<GetHealthResponse> GetHealth()
{
return this.GetAsync<GetHealthResponse>("health");
}
}
class GetHealthResponse
{
public int Version { get; set; }
}
}
Handling datetimes
To handle ISO-8601 datetime strings you'll have to write a converter for datetimes.
using System;
using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace Api.Client
{
class Program
{
static async Task Main(string[] args)
{
var api = new Client("http://127.0.0.1:3030");
var healthResult = await api.GetHealth();
Console.WriteLine(healthResult.ModifiedDate);
}
}
class Client
{
static JsonSerializerOptions JsonOptions = new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
};
readonly string targetBase;
readonly HttpClient httpClient;
static Client()
{
JsonOptions.Converters.Add(new DateTimeConverter());
}
public Client(string targetBase)
{
this.targetBase = targetBase;
this.httpClient = new HttpClient();
}
private async Task<T> GetAsync<T>(string path)
{
var response = await this.httpClient.GetAsync($"{this.targetBase}/{path}");
using (var responseStream = await response.Content.ReadAsStreamAsync())
{
return await JsonSerializer.DeserializeAsync<T>(responseStream, JsonOptions);
}
}
public Task<GetHealthResponse> GetHealth()
{
return this.GetAsync<GetHealthResponse>("health");
}
}
class GetHealthResponse
{
public int Version { get; set; }
public DateTime ModifiedDate { get; set; }
}
public class DateTimeConverter : JsonConverter<DateTime>
{
public override DateTime Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
// an additional allocation because datetime parse
// accepts ReadonlySpan<char> while Utf8JsonReader.ValueSpan
// returns a ReaonlySpan<byte>
return DateTime.Parse(reader.GetString()).ToUniversalTime();
}
public override void Write(
Utf8JsonWriter writer,
DateTime value,
JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToUniversalTime().ToString("o"));
}
}
}
Conclusion
The System.Text.Json serializer is quite clean and fast, and a good alternative to Newtonsoft.JSON.