System.Text.Json 序列化对象

System.Text.Json Serialize an Object

提问人:Foxhunt 提问时间:11/7/2023 最后编辑:dbcFoxhunt 更新时间:11/8/2023 访问量:130

问:

我正在尝试通过 SignalR 发送对象。 它看起来像

[Serializable]
public class MyDataClass
{
   //Some property correctly serialized and deserialized

   public Int32 Format { get; set; }

   public required Object? Value { get; set;}
} 

Value 可以是 bool、string 、int ...,并由 Format 描述。

它序列化得很好。

但是当我反序列化时,我有一个,打印它时,它看起来像“ValueKind ({})”System.Text.Json.JsonElement

编辑:使用自定义 JsonConverter

使用建议的文档,我编写了一个简单的 JsonConverter 来处理这个问题:

public class MyDataClassConverter : JsonConverter<MyDataClass>
{
    public override ViewModelExecutionVariable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var myData = new MyDataClass();
        var properties = typeof(MyDataClass).GetProperties(BindingFlags.Instance | BindingFlags.Public);

        using (JsonDocument doc = JsonDocument.ParseValue(ref reader))
        {
            foreach (var propertyInfo in properties)
            {
                if (doc.RootElement.TryGetProperty(propertyInfo.Name, out var jsonElement))
                {
                    object? value = JsonSerializer.Deserialize(jsonElement.GetRawText(), propertyInfo.PropertyType, options);
                    propertyInfo.SetValue(myData , value);
                }
            }
        }
        myData.Value = FormatHelper.GetObjectValue(myData.Format, ((JsonElement)myData.Value).GetString())!;

        return myData;
    }

    public override void Write(Utf8JsonWriter writer, MyDataClass value, JsonSerializerOptions options)
    {
        JsonSerializer.Serialize(writer, value, options);
    }
}

并添加了装饰器 JsonConverter(typeof(MyDataClassConverter))]

但是现在我在序列化项目列表时遇到了一个问题:

List<MyDataList> myDataList = this.GetDataList();
var json = System.Text.Json.JsonSerializer.Serialize(myDataList);

在最后一行之后,程序永远不会结束。

它在没有装饰器的情况下工作正常,不使用自定义序列化程序:

System.Text.Json.JsonSerializer.Serialize(myDataList)

然后是自定义解串程序

System.Text.Json.JsonSerializer.Deserialize<List<MyDataClass>>(message, new JsonSerializerOptions {Converters = { new MyDataClassConverter()}})!)

但它比装饰器重得多,所以我会接受任何建议来制作 Serialize 作品。

为了增加精度,FormatHelper.GetObjectValue() 是一个很大的开关:

public static Object? GetObjectValue(Int32 formatType, String? value)
{
    Object? result = null;
    switch (formatType)
    {
        case FormatTypeBase.Boolean:
            result = value == "1" || value == "True";
            break;
        case FormatTypeBase.Numeric:
            result = Double.Parse(value, CultureInfo.InvariantCulture);
            break;
        case FormatTypeBase.String:
            result = value;
            break;
        //...
    }
    return result 
}
C# JSON 序列化 system.text.json

评论

3赞 Charlieface 11/7/2023
这回答了你的问题吗?System.Text.Json 中是否可以进行多态反序列化?
2赞 Charlieface 11/7/2023
请参阅第二个答案,新功能现已可用。
0赞 dbc 11/7/2023
保证是原始的吗?如果是这样,您可能希望使用自定义 JsonConverter 来识别 JSON 基元并将其映射到 .NET 基元,而不是使用多态反序列化,例如从 C# 的答案 - 将嵌套的 json 反序列化为嵌套的 Dictionary<string, object>ValueObjectAsPrimitiveConverter
0赞 dbc 11/7/2023
什么?你能分享一下代码吗?FormatHelper.GetObjectValue()
0赞 Foxhunt 11/7/2023
当然,但它仅在反序列化中使用,我用它进行了测试,它工作得很好System.Text.Json.JsonSerializer.Deserialize<List<MyDataClass>>(message, new JsonSerializerOptions {Converters = { new MyDataClassConverter()}})!)

答:

1赞 dbc 11/8/2023 #1

您的转换器基本上只是使用第二个属性()的值对一个属性(特别是)进行后处理。这可以通过实现 IJsonOnDeserialized 反序列化回调来最容易地完成:MyDataClass.ValueFormat

public class MyDataClass : IJsonOnDeserialized
{
    //Some properties correctly serialized and deserialized

    //The Value I need to deserialize to the correct type, and its format.
    public Int32 Format { get; set; } // Shouldn't this be a FormatTypeBase enum rather than an integer?
    public required Object? Value { get; set;}
    
    // A callback to convert the deserialized JsonElement to the correct primitive.
    void IJsonOnDeserialized.OnDeserialized() 
    {
        if (Value is JsonElement e)
        {
            Value = FormatHelper.GetObjectValue(Format, e.ValueKind == JsonValueKind.String ? e.GetString() : e.GetRawText());
        }
    }
}       

public enum FormatTypeBase
{
    Boolean = 1,
    Numeric = 2,
    String = 3,
}

public class FormatHelper
{
    public static Object? GetObjectValue(Int32 formatType, String? value)
    {
        if (value == null)
            return null;  // Or throw an exception?
        Object? result = null;
        switch ((FormatTypeBase)formatType)
        {
            case FormatTypeBase.Boolean:
                result = value == "1" || value.Equals("true", StringComparison.OrdinalIgnoreCase); // TODO: check for invalid non-Boolean string values.
                break;
            case FormatTypeBase.Numeric:
                result = Double.Parse(value, CultureInfo.InvariantCulture);
                break;
            case FormatTypeBase.String:
                result = value;
                break;
            //...
            default:
                throw new NotImplementedException(((FormatTypeBase)formatType).ToString());
        }
        return result;
    }       
}

以这种方式执行操作完全消除了在序列化期间编写回退到默认逻辑的需要。虽然有点棘手,但这是可能的 - 尽管如果您将转换器直接应用于类型,则不会。有关详细信息,请参阅如何在自定义 System.Text.Json JsonConverter 中使用默认序列化?答案JsonConverter

在这里演示小提琴。