【问题标题】:System.Text.Json Custom JsonConverter Write() never calledSystem.Text.Json 自定义 JsonConverter Write() 从未调用过
【发布时间】:2022-12-14 07:45:57
【问题描述】:

我有一个 .NET 6 解决方案,我试图在调用 JsonObject.ToJsonString() 时覆盖 DateTimeOffset 的默认格式。这都是使用本地System.Text.Json 库。

我添加了一个自定义DateTimeOffsetConverter

public class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
    private readonly string _format;

    public DateTimeOffsetConverter(string format)
    {
        _format = format;
    }

    public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        Debug.Assert(typeToConvert == typeof(DateTimeOffset));
        return DateTimeOffset.Parse(reader.GetString());
    }

    public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString(_format));
    }

    public override bool CanConvert(Type typeToConvert)
    {
        return (typeToConvert == typeof(DateTimeOffset));
    }
}

但是当我尝试使用它时,代码永远不会超出被调用的构造函数。

我错过了什么阻止JsonConverter被调用?

这是我尝试使用该功能的代码:

[Theory]
[InlineData("New Zealand Standard Time")]
[InlineData("India Standard Time")]
[InlineData("Central Brazilian Standard Time")]
[InlineData("W. Australia Standard Time")]
public void DateTimeOffsetIsSerializedCorrectlyTest(string timeZoneId)
{
    const string DateTimeFormat = "yyyy-MM-ddTHH:mm:ss.fffzzz";
    var timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
    var dateTimeOffset = new DateTimeOffset(DateTimeOffset.Now.DateTime, timeZoneInfo.BaseUtcOffset);

    var json = new JsonObject
    {
        { "value", dateTimeOffset }
    };

    var options = new JsonSerializerOptions
    {
        Converters = { new DateTimeOffsetConverter(DateTimeFormat) }
    };
    string jsonString = json.ToJsonString(options);

    Assert.Contains(jsonString, dateTimeOffset.ToString(DateTimeFormat));
}

已经发布了许多密切相关的问题,我已经尝试过谁的解决方案,但似乎没有一个能解决我的确切情况。

【问题讨论】:

    标签: c# json .net-6.0 system.text.json json-serialization


    【解决方案1】:

    看起来好像在 DateTimeOffset 时应用了转换器转换为JsonNode而不是当 JsonNode格式化为字符串.因此,您可以通过 explicitly serializing 您的 DateTimeOffset 生成所需的 JSON 而不是依赖 implicit conversion

    var options = new JsonSerializerOptions
    {
        Converters = { new DateTimeOffsetConverter(DateTimeFormat) }
    };
    
    var json = new JsonObject
    {
        { "value", JsonSerializer.SerializeToNode(dateTimeOffset, options) }
    };
    

    这导致 {"value":"2022-11-27T15:10:23.570u002B12:00"}。请注意,+ 已转义。如果您要求它不被转义以便您的Assert.Contains(jsonString, dateTimeOffset.ToString(DateTimeFormat)); 成功通过,请使用UnsafeRelaxedJsonEscaping

    var formattingOptions = new JsonSerializerOptions
    {
        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
        // Other formatting options as required, e.g.
        //WriteIndented = true, // This option also seems to be applied when the JsonNode is formatted to a string, rather than when it is constructed
    };
    
    string jsonString = json.ToJsonString(formattingOptions);
    

    结果是{"value":"2022-11-27T15:17:44.142+12:00"}

    演示小提琴 #1 here

    笔记:

    • 文档页面How to use a JSON document, Utf8JsonReader, and Utf8JsonWriter in System.Text.Json: JsonNode with JsonSerializerOptions指出:

      您可以使用 JsonSerializer 序列化和反序列化 JsonNode 的实例。但是,如果您使用采用 JsonSerializerOptions 的重载,则选项实例仅用于获取自定义转换器。不使用选项实例的其他功能。 ...

      这种说法似乎是不正确的;即使您使用 JsonSerializer.Serialize(json, options) 序列化您的 JsonNode 层次结构,您的 DateTimeOffsetConverter 也不会被提取。

      演示小提琴 #2 here

    • 我找不到任何文档列出在 JsonNode 构造期间应用的选项与 JsonNode 字符串格式。文档确实显示了WriteIndented is applied during string formatting。一些实验表明,此外,Encoder 和(令人惊讶的)NumberHandling 在字符串格式化期间被应用。

      演示小提琴 #3 here

    • 一些调试显示了为什么你的 DateTimeOffsetConverter 在字符串格式化期间没有被应用。 DateTimeOffset to JsonNode implicit conversion operator

      JsonNode node = dateTimeOffset;
      

      返回 JsonValueTrimmable&lt;DateTimeOffset&gt; 类型的对象。这种类型包括它自己的内部转换器:

      internal sealed partial class JsonValueTrimmable<TValue> : JsonValue<TValue>
      {
          private readonly JsonTypeInfo<TValue>? _jsonTypeInfo;
          private readonly JsonConverter<TValue>? _converter;
      

      如果我们通过反射访问_converter的值,我们发现它被初始化为系统转换器DateTimeOffsetConverter

      var fi = node.GetType().GetField("_converter", BindingFlags.NonPublic | BindingFlags.Instance);
      Console.WriteLine("JsonValueTrimmable<T>._converter = {0}", fi.GetValue(node)); // Prints System.Text.Json.Serialization.Converters.DateTimeOffsetConverter
      

      这个系统转换器忽略传入的选项并简单地调用Utf8JsonWriter.WriteStringValue(DateTimeOffset);

      演示小提琴 #4 here

    【讨论】:

    • 感谢您提供令人难以置信的详细解释,非常感谢:)
    猜你喜欢
    • 2020-10-05
    • 1970-01-01
    • 2020-05-01
    • 2014-08-28
    • 2013-07-25
    • 1970-01-01
    • 2022-12-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多