使用System.Text.Json反序列化.NET标准库不可变类型的方法
System.Text.Json 反序列化 ActivityContext 返回默认值的解决方案
问题描述
使用 System.Text.Json 序列化/反序列化 .NET 内置结构体 System.Diagnostics.ActivityContext 时,序列化输出正常,但反序列化得到的实例所有属性均为默认值:
- 按照官方不可变类型序列化规则,需要给类型构造函数添加
[JsonConstructor]特性才能正确反序列化,但ActivityContext是标准库内置类型,无法直接修改源码添加特性 - 已单独为结构体的
ActivityTraceId、ActivitySpanId属性类型编写了自定义JSON转换器,仍然无法解决反序列化问题 - 场景为测试OpenTelemetry上下文传播的简单JSON序列化逻辑,非生产业务问题
测试代码
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()); }
测试输出
5ac8dcec05989c8d455955f73ecb2560 {"TraceId":"5ac8dcec05989c8d455955f73ecb2560","SpanId":"96d3440ed8eff463","TraceFlags":0,"TraceState":null,"IsRemote":false} 00000000000000000000000000000000
问题原因
- 仅为属性的类型编写自定义转换器,无法解决
ActivityContext本身的构造逻辑问题 ActivityContext是没有公开无参构造函数的不可变值类型,源码未标注[JsonConstructor],System.Text.Json 反序列化时无法匹配对应的参数化构造函数完成属性赋值,最终直接返回结构体的默认值,导致所有属性都是0/空的默认状态- 对于无法修改源码的内置类型,不需要强行给类型加特性,直接为该类型编写专属自定义JSON转换器即可解决问题。
实现方案
编写 ActivityContext 专属转换器
不需要删除已有的ActivityTraceIdJsonConverter和ActivitySpanIdJsonConverter,新增如下针对ActivityContext的转换器即可,转换器内部会自动复用已注册的子类型转换器:
public class ActivityContextJsonConverter : JsonConverter<ActivityContext> { public override ActivityContext Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException("预期为JSON对象起始标记"); ActivityTraceId traceId = default; ActivitySpanId spanId = default; ActivityTraceFlags traceFlags = ActivityTraceFlags.None; string? traceState = null; bool isRemote = false; while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) { // 读取完所有属性后,调用公开构造函数返回实例 return new ActivityContext(traceId, spanId, traceFlags, traceState, isRemote); } if (reader.TokenType != JsonTokenType.PropertyName) throw new JsonException("预期为属性名"); string propName = reader.GetString()!; reader.Read(); switch (propName) { case nameof(ActivityContext.TraceId): traceId = JsonSerializer.Deserialize<ActivityTraceId>(ref reader, options); break; case nameof(ActivityContext.SpanId): spanId = JsonSerializer.Deserialize<ActivitySpanId>(ref reader, options); break; case nameof(ActivityContext.TraceFlags): traceFlags = (ActivityTraceFlags)reader.GetInt32(); break; case nameof(ActivityContext.TraceState): traceState = reader.TokenType == JsonTokenType.Null ? null : reader.GetString(); break; case nameof(ActivityContext.IsRemote): isRemote = reader.GetBoolean(); break; default: reader.Skip(); break; } } throw new JsonException("JSON格式不完整,缺少对象结束标记"); } public override void Write(Utf8JsonWriter writer, ActivityContext value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WritePropertyName(nameof(ActivityContext.TraceId)); JsonSerializer.Serialize(writer, value.TraceId, options); writer.WritePropertyName(nameof(ActivityContext.SpanId)); JsonSerializer.Serialize(writer, value.SpanId, options); writer.WriteNumber(nameof(ActivityContext.TraceFlags), (int)value.TraceFlags); writer.WriteString(nameof(ActivityContext.TraceState), value.TraceState); writer.WriteBoolean(nameof(ActivityContext.IsRemote), value.IsRemote); writer.WriteEndObject(); } }
使用方式
将新的转换器加入JsonSerializerOptions的Converters列表即可:
var options = new JsonSerializerOptions() { Converters = { new ActivityTraceIdJsonConverter(), new ActivitySpanIdJsonConverter(), new ActivityContextJsonConverter() // 新增的转换器 } };
简化技巧:如果觉得逐属性读取JSON繁琐,也可以在转换器的
Read方法中先将JSON反序列化为一个临时的普通POCO类(所有属性带public set,结构和ActivityContext一致),读取完POCO后再把POCO的属性传入ActivityContext的构造函数生成正式实例,代码会更简洁。
内容的提问来源于stack exchange,提问作者Algirdas
相关产品推荐
相关产品推荐

