1

我有一个场景,我必须将 a 序列化为DateTimeOffsetJSON 作为一个简单的字符串值(例如"2021-04-07T18:18:00.000Z",但将其反序列化为嵌入在对象中的值(例如{"_date":"2021-04-07T18:18:00.000Z"})。我该如何做到这一点System.Text.Json

详细情况如下。我正在为 blazor 包装一个日历 UI 库。我已经用 C# 类包装了 JS 类。从 Blazor 向 JS 发送日历“计划”(事件)时,DateTimes 只是作为序列化字符串传递。这行得通。

当从 JS 向 Blazor 发送“时间表”时,它会以对象(TZdate)的形式返回,内部是一个“_date”属性。

返回的 json 对象如下所示:

{
"end":{
      "_date":"2021-04-07T18:18:00.000Z"
       }
}

我试过为 DateTimeOffset 编写一个自定义转换器。这是读取方法:

        public override DateTimeOffset Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options) =>
            DateTimeOffset.ParseExact(JsonDocument.Parse(reader.GetString()).RootElement.EnumerateObject().First().Value.EnumerateObject().First().Value.GetString(),
             TZDateFormat, CultureInfo.InvariantCulture);

以上不起作用。我不相信我正在reader正确使用该类或如何从此处的 json 中提取“_date”。

或者,我尝试用新的“TZDate”类和属性“_date”包装我的 DateTimeOffset 属性,但这在 JS 端中断,因为库期望来自 C# 的简单日期时间字符串,而不是对象。

更改 JS 库可能不是选项。

我有什么选择或如何解决这个问题?

4

1 回答 1

1

您的DateTimeOffset值嵌入在一个对象中,如下所示:

{"_date":"2021-04-07T18:18:00.000Z"}

并且您想提取_date要返回的内部属性的值。您可以使用以下JsonConverter<DateTime>.Read()方法执行此操作:

public class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
    const string TZDateFormat = "O"; // Your custom format (not shown in your question).

    public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) =>
        // Write as a simple string.
        writer.WriteStringValue(value.ToString(TZDateFormat, CultureInfo.InvariantCulture));
    
    const string _date = "_date";

    public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        switch (reader.TokenType)
        {
            case JsonTokenType.String:
                // A simple DateTimeOffset string "value"
                return DateTimeOffset.ParseExact(reader.GetString(), TZDateFormat, CultureInfo.InvariantCulture);
            case JsonTokenType.StartObject:
            {
                // A DateTimeOffset string embedded in an object { "_date" : "value" }
                using var doc = JsonDocument.ParseValue(ref reader);
                if (doc.RootElement.TryGetProperty(_date, out var value))
                    return DateTimeOffset.ParseExact(value.GetString(), TZDateFormat, CultureInfo.InvariantCulture);
                return default(DateTimeOffset); // Or throw an exception?
            }
            default:
                throw new JsonException(); // Unknown token type
        }
    }
}

在您的Read()方法中,您尝试将返回的值加载reader.GetString()到 aJsonDocument中,但在方法的开头,阅读器位于StartObject令牌而不是值字符串上,并且reader.GetString()只返回当前令牌的字符串值而不是当前令牌及其你似乎想要的孩子。要将当前令牌及其子代加载到 aJsonDocument中,请使用JsonDocument.ParseValue(Utf8JsonReader).

(顺便说一句,JsonDocument是一次性的,实际上必须将池化的内存返回到内存池。)

如果您想避免构建 a JsonDocument,您可以直接使用Utf8JsonReader以下方式通过 JSON 流式传输:

static byte [] _date = Encoding.UTF8.GetBytes("_date");

public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    switch (reader.TokenType)
    {
        case JsonTokenType.String:
            // A simple DateTimeOffset string "value"
            return DateTimeOffset.ParseExact(reader.GetString(), TZDateFormat, CultureInfo.InvariantCulture);
        case JsonTokenType.StartObject:
        {
            // A DateTimeOffset string embedded in an object { "_date" : "value" }
            DateTimeOffset? value = null;
            while (reader.Read())
            {
                switch (reader.TokenType)
                {
                    case JsonTokenType.EndObject:
                        return value.GetValueOrDefault();
                    case JsonTokenType.PropertyName:
                        var match = reader.ValueTextEquals(_date);
                        reader.Read();
                        if (match)
                            value = DateTimeOffset.ParseExact(reader.GetString(), TZDateFormat, CultureInfo.InvariantCulture);
                        else
                            reader.Skip();
                        break;
                    default:
                        throw new JsonException();
                }
            }
        }
        break;
    }
    throw new JsonException();
}

这有点复杂,但也应该更高效。

在这两种情况下,我都会检查传入的值是对象还是简单的字符串。如果是一个简单的字符串,我会继续将其解析为DateTimeOffset.

演示小提琴在这里

于 2021-04-14T05:23:43.993 回答