Skip to content

Commit a4e5746

Browse files
committed
Adding "Registering custom System.Text.Json converters"
1 parent 9d9bccd commit a4e5746

File tree

2 files changed

+126
-77
lines changed

2 files changed

+126
-77
lines changed

docs/client-concepts/serialization/custom-serialization.asciidoc

+44-6
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,6 @@ The index request is serialized, with the source serializer handling the `Person
3737
}
3838
----
3939

40-
[[registering-custom-converters]]
41-
===== Registering custom `System.Text.Json` converters
42-
43-
TODO
44-
4540
[[configuring-custom-jsonserializeroptions]]
4641
===== Configuring custom `JsonSerializerOptions`
4742

@@ -61,7 +56,7 @@ Our application defines the following `Person` class, which models a document we
6156
include::{doc-tests-src}/ClientConcepts/Serialization/CustomSerializationTests.cs[tag=person-class]
6257
----
6358

64-
We want to serialize our source document using Pascal Casing for the JSON properties. Since the options applied in the `DefaultSouceSerializer` set the `PropertyNamingPolicy` to `JsonNamingPolicy.CamelCase`, we must override this setting.
59+
We want to serialize our source document using Pascal Casing for the JSON properties. Since the options applied in the `DefaultSouceSerializer` set the `PropertyNamingPolicy` to `JsonNamingPolicy.CamelCase`, we must override this setting. After configuring the `ElasticsearchClientSettings` we can index our document to {es}.
6560

6661
[source,csharp]
6762
----
@@ -92,6 +87,49 @@ As an alternative to using a local function, we could store an `Action<JsonSeria
9287
include::{doc-tests-src}/ClientConcepts/Serialization/CustomSerializationTests.cs[tag=custom-options-action]
9388
----
9489

90+
[[registering-custom-converters]]
91+
===== Registering custom `System.Text.Json` converters
92+
93+
In certain more advanced situations, you may have types which require further customization during serialization than is possible using `System.Text.Json` property attributes. In these cases, the recommendation from Microsoft is to leverage a custom `JsonConverter`. Source document types serialized using the `DefaultSourceSerializer` can leverage the power of custom converters.
94+
95+
For the example, we have a document class in our application which should use a legacy JSON structure to continue operating with existing indexed documents. Several options are available, but we'll apply a custom converter in this case.
96+
97+
Our class is defined and the `JsonConverter` attribute is applied to the class type, specifying the type of a custom converter.
98+
99+
[source,csharp]
100+
----
101+
include::{doc-tests-src}/ClientConcepts/Serialization/CustomSerializationTests.cs[tag=usings]
102+
include::{doc-tests-src}/ClientConcepts/Serialization/CustomSerializationTests.cs[tag=customer-with-jsonconverter-attribute]
103+
----
104+
<1> The `JsonConverter` is used to signal to `System.Text.Json` that it should use a converter of type `CustomerConverter` when serializing instances of this class.
105+
106+
When serializing this class, rather than include a string value representing the value of the `CustomerType` property, we are required to send a boolean property named `isStandard`. This requirement can be achieved with a custom JsonConverter implementation.
107+
108+
[source,csharp]
109+
----
110+
include::{doc-tests-src}/ClientConcepts/Serialization/CustomSerializationTests.cs[tag=converter-usings]
111+
include::{doc-tests-src}/ClientConcepts/Serialization/CustomSerializationTests.cs[tag=customer-converter]
112+
----
113+
<1> When reading, this converter will read the `isStandard` boolean and translate this to the correct `CustomerType` enum value.
114+
<2> When writing, this converter will translate the `CustomerType` enum value to a `isStandard` boolean property.
115+
116+
We can then index a customer document into {es}.
117+
118+
[source,csharp]
119+
----
120+
include::{doc-tests-src}/ClientConcepts/Serialization/CustomSerializationTests.cs[tag=index-customer-with-converter]
121+
----
122+
123+
The `Customer` instance is serialized, using the custom converter, resulting in the following JSON document.
124+
125+
[source,javascript]
126+
----
127+
{
128+
"customerName": "Customer Ltd",
129+
"isStandard": false
130+
}
131+
----
132+
95133
[[injecting-custom-serializer]]
96134
===== Injecting a custom serializer
97135

tests/Tests/Documentation/ClientConcepts/Serialization/CustomSerializationTests.cs

+82-71
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010

1111
#pragma warning disable IDE0005
1212
//tag::usings[]
13+
//tag::converter-usings[]
1314
using System;
1415
using System.Text.Json;
1516
//tag::usings-serialization[]
1617
using System.Text.Json.Serialization;
1718
//end::usings-serialization[]
19+
//end::converter-usings[]
1820
using System.Threading.Tasks;
1921
using Elastic.Transport;
2022
using Elastic.Clients.Elasticsearch;
@@ -37,7 +39,8 @@ public async Task CustomizingJsonSerializerOptions()
3739

3840
#pragma warning disable format
3941
//tag::custom-options-local-function[]
40-
static void ConfigureOptions(JsonSerializerOptions o) => o.PropertyNamingPolicy = null; // <1>
42+
static void ConfigureOptions(JsonSerializerOptions o) => // <1>
43+
o.PropertyNamingPolicy = null;
4144
//end::custom-options-local-function[]
4245

4346
//tag::create-client[]
@@ -77,13 +80,13 @@ public async Task CustomizingJsonSerializerOptions()
7780

7881
// Alternative example using an Action
7982
//tag::custom-options-action[]
80-
Action<JsonSerializerOptions> configureOptions = o => o.PropertyNamingPolicy = null; // <3>
83+
Action<JsonSerializerOptions> configureOptions = o => o.PropertyNamingPolicy = null;
8184
//end::custom-options-action[]
8285
}
8386

8487
#pragma warning disable format
8588
//tag::person-class[]
86-
private class Person
89+
public class Person
8790
{
8891
public string FirstName { get; set; }
8992
}
@@ -115,7 +118,7 @@ public async Task UsingSystemTextJsonConverterAttributes()
115118
{
116119
#pragma warning disable format
117120
//tag::index-customer-with-converter[]
118-
var customer = new Customer { CustomerName = "Customer Ltd", Type = CustomerType.Enhanced };
121+
var customer = new Customer { CustomerName = "Customer Ltd", CustomerType = CustomerType.Enhanced };
119122
var indexResponse = await Client.IndexAsync(customer, "my-index-name");
120123
//end::index-customer-with-converter[]
121124
#pragma warning restore format
@@ -126,12 +129,12 @@ public async Task UsingSystemTextJsonConverterAttributes()
126129
var ms = new MemoryStream(indexResponse.ApiCallDetails.RequestBodyInBytes);
127130
var deserializedCustomer = Client.SourceSerializer.Deserialize<Customer>(ms);
128131
deserializedCustomer.CustomerName.Should().Be("Customer Ltd");
129-
deserializedCustomer.Type.Should().Be(CustomerType.Enhanced);
132+
deserializedCustomer.CustomerType.Should().Be(CustomerType.Enhanced);
130133
}
131134

132135
#pragma warning disable format
133136
//tag::person-class-with-attributes[]
134-
private class Person
137+
public class Person
135138
{
136139
[JsonPropertyName("forename")] // <1>
137140
public string FirstName { get; set; }
@@ -142,88 +145,96 @@ private class Person
142145
//end::person-class-with-attributes[]
143146
#pragma warning restore format
144147

145-
[JsonConverter(typeof(CustomerConverter))]
146-
private class Customer
147-
{
148-
public string CustomerName { get; set; }
149-
public CustomerType Type { get; set; }
150-
}
148+
#pragma warning disable format
149+
//tag::customer-with-jsonconverter-attribute[]
150+
[JsonConverter(typeof(CustomerConverter))] // <1>
151+
public class Customer
152+
{
153+
public string CustomerName { get; set; }
154+
public CustomerType CustomerType { get; set; }
155+
}
151156

152-
private enum CustomerType
153-
{
154-
Standard,
155-
Enhanced
156-
}
157+
public enum CustomerType
158+
{
159+
Standard,
160+
Enhanced
161+
}
162+
//end::customer-with-jsonconverter-attribute[]
163+
#pragma warning restore format
157164

158-
private class CustomerConverter : JsonConverter<Customer>
165+
#pragma warning disable format
166+
//tag::customer-converter[]
167+
public class CustomerConverter : JsonConverter<Customer>
168+
{
169+
public override Customer Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
159170
{
160-
public override Customer Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
161-
{
162-
var customer = new Customer();
171+
var customer = new Customer();
163172

164-
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
173+
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
174+
{
175+
if (reader.TokenType == JsonTokenType.PropertyName)
165176
{
166-
if (reader.TokenType == JsonTokenType.PropertyName)
177+
if (reader.ValueTextEquals("customerName"))
167178
{
168-
if (reader.ValueTextEquals("customerName"))
179+
reader.Read();
180+
customer.CustomerName = reader.GetString();
181+
continue;
182+
}
183+
184+
if (reader.ValueTextEquals("isStandard")) // <1>
185+
{
186+
reader.Read();
187+
var isStandard = reader.GetBoolean();
188+
189+
if (isStandard)
169190
{
170-
reader.Read();
171-
customer.CustomerName = reader.GetString();
172-
continue;
191+
customer.CustomerType = CustomerType.Standard;
173192
}
174-
175-
if (reader.ValueTextEquals("isStandard"))
193+
else
176194
{
177-
reader.Read();
178-
var isStandard = reader.GetBoolean();
179-
180-
if (isStandard)
181-
{
182-
customer.Type = CustomerType.Standard;
183-
}
184-
else
185-
{
186-
customer.Type = CustomerType.Enhanced;
187-
}
188-
189-
continue;
195+
customer.CustomerType = CustomerType.Enhanced;
190196
}
197+
198+
continue;
191199
}
192200
}
193-
194-
return customer;
195201
}
196202

197-
public override void Write(Utf8JsonWriter writer, Customer value, JsonSerializerOptions options)
198-
{
199-
if (value is null)
200-
{
201-
writer.WriteNullValue();
202-
return;
203-
}
203+
return customer;
204+
}
204205

205-
writer.WriteStartObject();
206+
public override void Write(Utf8JsonWriter writer, Customer value, JsonSerializerOptions options)
207+
{
208+
if (value is null)
209+
{
210+
writer.WriteNullValue();
211+
return;
212+
}
206213

207-
if (!string.IsNullOrEmpty(value.CustomerName))
208-
{
209-
writer.WritePropertyName("customerName");
210-
writer.WriteStringValue(value.CustomerName);
211-
}
214+
writer.WriteStartObject();
212215

213-
writer.WritePropertyName("isStandard");
216+
if (!string.IsNullOrEmpty(value.CustomerName))
217+
{
218+
writer.WritePropertyName("customerName");
219+
writer.WriteStringValue(value.CustomerName);
220+
}
214221

215-
if (value.Type == CustomerType.Standard)
216-
{
217-
writer.WriteBooleanValue(true);
218-
}
219-
else
220-
{
221-
writer.WriteBooleanValue(false);
222-
}
222+
writer.WritePropertyName("isStandard");
223223

224-
writer.WriteEndObject();
224+
if (value.CustomerType == CustomerType.Standard) // <2>
225+
{
226+
writer.WriteBooleanValue(true);
225227
}
228+
else
229+
{
230+
writer.WriteBooleanValue(false);
231+
}
232+
233+
writer.WriteEndObject();
226234
}
235+
}
236+
//end::customer-converter[]
237+
#pragma warning restore format
227238

228239
private class CustomerTypeConverter : JsonConverter<CustomerType>
229240
{
@@ -296,19 +307,19 @@ public async Task DerivingFromSystemTextJsonSerializer_ToRegisterACustomEnumConv
296307
deserializedCustomer.Type.Should().Be(CustomerType.Enhanced);
297308
}
298309

299-
private class Customer
310+
public class Customer
300311
{
301312
public string CustomerName { get; set; }
302313
public CustomerType Type { get; set; }
303314
}
304315

305-
private enum CustomerType
316+
public enum CustomerType
306317
{
307318
Standard,
308319
Enhanced
309320
}
310321

311-
private class MyCustomSerializer : SystemTextJsonSerializer
322+
public class MyCustomSerializer : SystemTextJsonSerializer
312323
{
313324
private readonly JsonSerializerOptions _options;
314325

@@ -324,7 +335,7 @@ public MyCustomSerializer(IElasticsearchClientSettings settings) : base(settings
324335
protected override JsonSerializerOptions CreateJsonSerializerOptions() => _options;
325336
}
326337

327-
private class CustomerTypeConverter : JsonConverter<CustomerType>
338+
public class CustomerTypeConverter : JsonConverter<CustomerType>
328339
{
329340
public override CustomerType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
330341
{

0 commit comments

Comments
 (0)