0

I am trying to serialize and deserialize System.Diagnostics.ActivityContext struct. According to documentation How to use immutable types and non-public accessors with System.Text.Json I need to add [JsonConstructor] attribute to struct constructor, but as struct is defined in standard library I can't do that. Is there any other way?

Or do I need to write custom converter for struct ActivityContext as I have done for its properties ActivityTraceId and ActivitySpanId? How would that look? Do I need to write whole deserialization logic to all properties?


Sample code:

using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;

ActivityContext testContext = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None);
Console.WriteLine(testContext.TraceId);

string? serializedActivityContext = JsonSerializer.Serialize(testContext, new JsonSerializerOptions()
{
    Converters = { new ActivityTraceIdJsonConverter(), new ActivitySpanIdJsonConverter()}
});

Console.WriteLine(serializedActivityContext);

ActivityContext deserializedActivityContext = JsonSerializer.Deserialize<ActivityContext>(serializedActivityContext, new JsonSerializerOptions()
{
    Converters = { new ActivityTraceIdJsonConverter(), new ActivitySpanIdJsonConverter()}
});

Console.WriteLine(deserializedActivityContext.TraceId);

public class ActivityTraceIdJsonConverter : JsonConverter<ActivityTraceId>
{
    public override ActivityTraceId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => ActivityTraceId.CreateFromString(reader.GetString());
    public override void Write(Utf8JsonWriter writer, ActivityTraceId value, JsonSerializerOptions options)
        => writer.WriteStringValue(value.ToString());
}
public class ActivitySpanIdJsonConverter : JsonConverter<ActivitySpanId>
{
    public override ActivitySpanId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => ActivitySpanId.CreateFromString(reader.GetString());
    public override void Write(Utf8JsonWriter writer, ActivitySpanId value, JsonSerializerOptions options)
        => writer.WriteStringValue(value.ToString());
}

Sample output:

5ac8dcec05989c8d455955f73ecb2560
{"TraceId":"5ac8dcec05989c8d455955f73ecb2560","SpanId":"96d3440ed8eff463","TraceFlags":0,"TraceState":null,"IsRemote":false}
00000000000000000000000000000000

P.S. not a real world problem. Was just playing with OpenTelemetry context propagation and got curious how would that look with simple json serialazition and got stuck on deserializing struct.

Algirdas
  • 1
  • 2
  • *Is there any other way?* -- currently writing a custom `JsonConverter` is the other way. With Newtonsoft you could make a custom contract resolver but System.Text.Json's contract is private, see [System.Text.Json API is there something like IContractResolver](https://stackoverflow.com/q/58926112/3744182) for confirmation. But custom converters don't need to be entirely manual, you can use reflection or deserialize to some intermediate DTO. – dbc Jun 27 '22 at 18:59
  • The fact that System.Text.Json will pick up the implicit public default constructor of a struct over a single explicit parameterized constructor even if present is a known limitation, see [Add direct support for structs when deserializing with System.Text.Json.Serialization #61264](https://github.com/dotnet/runtime/issues/61264). But honestly it's not too hard to write custom converters as long as you use some intermediate DTO, see e.g. https://dotnetfiddle.net/y59jj0. Does that answer your question? – dbc Jun 27 '22 at 19:45

0 Answers0