Skip to content

Commit 8dfd95a

Browse files
committed
Add OnSerialize callbacks to POCOs
1 parent 50413ea commit 8dfd95a

File tree

10 files changed

+604
-19
lines changed

10 files changed

+604
-19
lines changed

src/libraries/System.Text.Json/ref/System.Text.Json.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,22 @@ public abstract partial class JsonValue : System.Text.Json.Nodes.JsonNode
733733
}
734734
namespace System.Text.Json.Serialization
735735
{
736+
public partial interface IJsonOnDeserialized
737+
{
738+
void OnDeserialized();
739+
}
740+
public partial interface IJsonOnDeserializing
741+
{
742+
void OnDeserializing();
743+
}
744+
public partial interface IJsonOnSerialized
745+
{
746+
void OnSerialized();
747+
}
748+
public partial interface IJsonOnSerializing
749+
{
750+
void OnSerializing();
751+
}
736752
public abstract partial class JsonAttribute : System.Attribute
737753
{
738754
protected JsonAttribute() { }

src/libraries/System.Text.Json/src/System.Text.Json.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@
9292
<Compile Include="System\Text\Json\Serialization\Attributes\JsonPropertyNameAttribute.cs" />
9393
<Compile Include="System\Text\Json\Serialization\Converters\JsonMetadataServicesConverter.cs" />
9494
<Compile Include="System\Text\Json\Serialization\IgnoreReferenceResolver.cs" />
95+
<Compile Include="System\Text\Json\Serialization\IJsonOnDeserialized.cs" />
96+
<Compile Include="System\Text\Json\Serialization\IJsonOnDeserializing.cs" />
97+
<Compile Include="System\Text\Json\Serialization\IJsonOnSerialized.cs" />
98+
<Compile Include="System\Text\Json\Serialization\IJsonOnSerializing.cs" />
9599
<Compile Include="System\Text\Json\Serialization\JsonSerializerContext.cs" />
96100
<Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Collections.cs" />
97101
<Compile Include="System\Text\Json\Serialization\Metadata\JsonMetadataServices.Converters.cs" />

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectDefaultConverter.cs

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert,
3636

3737
obj = jsonTypeInfo.CreateObject!()!;
3838

39+
if (obj is IJsonOnDeserializing onDeserializing)
40+
{
41+
onDeserializing.OnDeserializing();
42+
}
43+
3944
// Process all properties.
4045
while (true)
4146
{
@@ -108,6 +113,11 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert,
108113

109114
obj = jsonTypeInfo.CreateObject!()!;
110115

116+
if (obj is IJsonOnDeserializing onDeserializing)
117+
{
118+
onDeserializing.OnDeserializing();
119+
}
120+
111121
state.Current.ReturnValue = obj;
112122
state.Current.ObjectState = StackFrameObjectState.CreatedObject;
113123
}
@@ -216,14 +226,21 @@ internal override bool OnTryRead(ref Utf8JsonReader reader, Type typeToConvert,
216226
}
217227
}
218228

229+
if (obj is IJsonOnDeserialized onDeserialized)
230+
{
231+
onDeserialized.OnDeserialized();
232+
}
233+
234+
// Unbox
235+
Debug.Assert(obj != null);
236+
value = (T)obj;
237+
219238
// Check if we are trying to build the sorted cache.
220239
if (state.Current.PropertyRefCache != null)
221240
{
222241
jsonTypeInfo.UpdateSortedPropertyCache(ref state.Current);
223242
}
224243

225-
value = (T)obj;
226-
227244
return true;
228245
}
229246

@@ -235,20 +252,24 @@ internal sealed override bool OnTryWrite(
235252
{
236253
JsonTypeInfo jsonTypeInfo = state.Current.JsonTypeInfo;
237254

238-
// Minimize boxing for structs by only boxing once here
239-
object objectValue = value!;
255+
object obj = value; // box once
240256

241257
if (!state.SupportContinuation)
242258
{
243259
writer.WriteStartObject();
244260
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
245261
{
246-
if (JsonSerializer.WriteReferenceForObject(this, objectValue, ref state, writer) == MetadataPropertyName.Ref)
262+
if (JsonSerializer.WriteReferenceForObject(this, obj, ref state, writer) == MetadataPropertyName.Ref)
247263
{
248264
return true;
249265
}
250266
}
251267

268+
if (obj is IJsonOnSerializing onSerializing)
269+
{
270+
onSerializing.OnSerializing();
271+
}
272+
252273
List<KeyValuePair<string, JsonPropertyInfo?>> properties = state.Current.JsonTypeInfo.PropertyCache!.List;
253274
for (int i = 0; i < properties.Count; i++)
254275
{
@@ -259,7 +280,7 @@ internal sealed override bool OnTryWrite(
259280
state.Current.DeclaredJsonPropertyInfo = jsonPropertyInfo;
260281
state.Current.NumberHandling = jsonPropertyInfo.NumberHandling;
261282

262-
bool success = jsonPropertyInfo.GetMemberAndWriteJson(objectValue, ref state, writer);
283+
bool success = jsonPropertyInfo.GetMemberAndWriteJson(obj, ref state, writer);
263284
// Converters only return 'false' when out of data which is not possible in fast path.
264285
Debug.Assert(success);
265286

@@ -275,14 +296,13 @@ internal sealed override bool OnTryWrite(
275296
state.Current.DeclaredJsonPropertyInfo = dataExtensionProperty;
276297
state.Current.NumberHandling = dataExtensionProperty.NumberHandling;
277298

278-
bool success = dataExtensionProperty.GetMemberAndWriteJsonExtensionData(objectValue, ref state, writer);
299+
bool success = dataExtensionProperty.GetMemberAndWriteJsonExtensionData(obj, ref state, writer);
279300
Debug.Assert(success);
280301

281302
state.Current.EndProperty();
282303
}
283304

284305
writer.WriteEndObject();
285-
return true;
286306
}
287307
else
288308
{
@@ -291,12 +311,17 @@ internal sealed override bool OnTryWrite(
291311
writer.WriteStartObject();
292312
if (options.ReferenceHandlingStrategy == ReferenceHandlingStrategy.Preserve)
293313
{
294-
if (JsonSerializer.WriteReferenceForObject(this, objectValue, ref state, writer) == MetadataPropertyName.Ref)
314+
if (JsonSerializer.WriteReferenceForObject(this, obj, ref state, writer) == MetadataPropertyName.Ref)
295315
{
296316
return true;
297317
}
298318
}
299319

320+
if (obj is IJsonOnSerializing onSerializing)
321+
{
322+
onSerializing.OnSerializing();
323+
}
324+
300325
state.Current.ProcessedStartToken = true;
301326
}
302327

@@ -310,7 +335,7 @@ internal sealed override bool OnTryWrite(
310335
state.Current.DeclaredJsonPropertyInfo = jsonPropertyInfo;
311336
state.Current.NumberHandling = jsonPropertyInfo.NumberHandling;
312337

313-
if (!jsonPropertyInfo.GetMemberAndWriteJson(objectValue!, ref state, writer))
338+
if (!jsonPropertyInfo.GetMemberAndWriteJson(obj!, ref state, writer))
314339
{
315340
Debug.Assert(jsonPropertyInfo.ConverterBase.ConverterStrategy != ConverterStrategy.Value ||
316341
jsonPropertyInfo.ConverterBase.TypeToConvert == JsonTypeInfo.ObjectType);
@@ -342,7 +367,7 @@ internal sealed override bool OnTryWrite(
342367
state.Current.DeclaredJsonPropertyInfo = dataExtensionProperty;
343368
state.Current.NumberHandling = dataExtensionProperty.NumberHandling;
344369

345-
if (!dataExtensionProperty.GetMemberAndWriteJsonExtensionData(objectValue, ref state, writer))
370+
if (!dataExtensionProperty.GetMemberAndWriteJsonExtensionData(obj, ref state, writer))
346371
{
347372
return false;
348373
}
@@ -366,9 +391,16 @@ internal sealed override bool OnTryWrite(
366391
state.Current.ProcessedEndToken = true;
367392
writer.WriteEndObject();
368393
}
394+
}
369395

370-
return true;
396+
if (obj is IJsonOnSerialized onSerialized)
397+
{
398+
onSerialized.OnSerialized();
371399
}
400+
401+
value = (T)obj; // unbox
402+
403+
return true;
372404
}
373405

374406
// AggressiveInlining since this method is only called from two locations and is on a hot path.
@@ -437,6 +469,11 @@ internal sealed override void CreateInstanceForReferenceResolver(ref Utf8JsonRea
437469

438470
object obj = state.Current.JsonTypeInfo.CreateObject!()!;
439471
state.Current.ReturnValue = obj;
472+
473+
if (obj is IJsonOnDeserializing onDeserializing)
474+
{
475+
onDeserializing.OnDeserializing();
476+
}
440477
}
441478
}
442479
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Object/ObjectWithParameterizedConstructorConverter.cs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo
3232

3333
ReadConstructorArguments(ref state, ref reader, options);
3434

35-
obj = CreateObject(ref state.Current);
35+
obj = (T)CreateObject(ref state.Current);
36+
37+
if (obj is IJsonOnDeserializing onDeserializing)
38+
{
39+
onDeserializing.OnDeserializing();
40+
}
3641

3742
if (argumentState.FoundPropertyCount > 0)
3843
{
@@ -91,7 +96,12 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo
9196
return false;
9297
}
9398

94-
obj = CreateObject(ref state.Current);
99+
obj = (T)CreateObject(ref state.Current);
100+
101+
if (obj is IJsonOnDeserializing onDeserializing)
102+
{
103+
onDeserializing.OnDeserializing();
104+
}
95105

96106
if (argumentState.FoundPropertyCount > 0)
97107
{
@@ -128,6 +138,17 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo
128138
}
129139
}
130140

141+
if (obj is IJsonOnDeserialized onDeserialized)
142+
{
143+
onDeserialized.OnDeserialized();
144+
}
145+
146+
EndRead(ref state);
147+
148+
// Unbox
149+
Debug.Assert(obj != null);
150+
value = (T)obj;
151+
131152
// Check if we are trying to build the sorted cache.
132153
if (state.Current.PropertyRefCache != null)
133154
{
@@ -140,10 +161,6 @@ internal sealed override bool OnTryRead(ref Utf8JsonReader reader, Type typeToCo
140161
state.Current.JsonTypeInfo.UpdateSortedParameterCache(ref state.Current);
141162
}
142163

143-
EndRead(ref state);
144-
145-
value = (T)obj;
146-
147164
return true;
148165
}
149166

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Text.Json.Serialization
5+
{
6+
/// <summary>
7+
/// Specifies that the JSON object should have its <see cref="OnDeserialized"/> method called
8+
/// after deserialization occurs.
9+
/// </summary>
10+
/// <remarks>
11+
/// Only JSON objects using the default custom converter support this behavior; collections, dictionaries and values do not.
12+
/// </remarks>
13+
public interface IJsonOnDeserialized
14+
{
15+
/// <summary>
16+
/// The method that is called after deserialization.
17+
/// </summary>
18+
void OnDeserialized();
19+
}
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Text.Json.Serialization
5+
{
6+
/// <summary>
7+
/// Specifies that the JSON object should have its <see cref="OnDeserializing"/> method called
8+
/// before deserialization occurs.
9+
/// </summary>
10+
/// <remarks>
11+
/// Only JSON objects using the default custom converter support this behavior; collections, dictionaries and values do not.
12+
/// </remarks>
13+
public interface IJsonOnDeserializing
14+
{
15+
/// <summary>
16+
/// The method that is called before deserialization.
17+
/// </summary>
18+
void OnDeserializing();
19+
}
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Text.Json.Serialization
5+
{
6+
/// <summary>
7+
/// Specifies that the JSON object should have its <see cref="OnSerialized"/> method called
8+
/// after serialization occurs.
9+
/// </summary>
10+
/// <remarks>
11+
/// Only JSON objects using the default custom converter support this behavior; collections, dictionaries and values do not.
12+
/// </remarks>
13+
public interface IJsonOnSerialized
14+
{
15+
/// <summary>
16+
/// The method that is called after serialization.
17+
/// </summary>
18+
void OnSerialized();
19+
}
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Text.Json.Serialization
5+
{
6+
/// <summary>
7+
/// Specifies that the JSON object should have its <see cref="OnSerializing"/> method called
8+
/// before serialization occurs.
9+
/// </summary>
10+
/// <remarks>
11+
/// Only JSON objects using the default custom converter support this behavior; collections, dictionaries and values do not.
12+
/// </remarks>
13+
public interface IJsonOnSerializing
14+
{
15+
/// <summary>
16+
/// The method that is called before serialization.
17+
/// </summary>
18+
void OnSerializing();
19+
}
20+
}

0 commit comments

Comments
 (0)