System.Text.Json:使用自动转换反序列化 JSON

Joh*_*ika 15 c# json.net .net-core asp.net-core system.text.json

使用 .Net Core 3 的新 System.Text.Json JsonSerializer,如何自动转换类型(例如 int 到 string 和 string 到 int)?例如,这会引发异常,因为id在 JSON 中是数字,而在 C# 中需要Product.Id一个字符串:

public class HomeController : Controller
{
    public IActionResult Index()
    {
        var json = @"{""id"":1,""name"":""Foo""}";
        var o = JsonSerializer.Deserialize<Product>(json, new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true,
        });

        return View();
    }
}

public class Product
{
    public string Id { get; set; }
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

Newtonsoft 的 Json.Net 很好地处理了这个问题。如果您在 C# 期望字符串时传入数值并不重要(反之亦然),一切都按预期反序列化。如果您无法控制作为 JSON 传入的类型格式,您如何使用 System.Text.Json 处理此问题?

itm*_*nus 17

  1. 新的System.Text.Jsonapi 公开了一个JsonConverterapi,它允许我们根据需要转换类型。

    例如,我们可以创建一个通用numberstring转换器:

    public class AutoNumberToStringConverter : JsonConverter<object>
    {
        public override bool CanConvert(Type typeToConvert)
        {
            return typeof(string) == typeToConvert;
        }
        public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if(reader.TokenType == JsonTokenType.Number) {
                return reader.TryGetInt64(out long l) ?
                    l.ToString():
                    reader.GetDouble().ToString();
            }
            if(reader.TokenType == JsonTokenType.String) {
                return reader.GetString();
            }
            using(JsonDocument document = JsonDocument.ParseValue(ref reader)){
                return document.RootElement.Clone().ToString();
            }
        }
    
        public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
        {
            writer.WriteStringValue( value.ToString());
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 当使用 MVC/Razor Page 时,我们可以在启动时注册这个转换器:

    services.AddControllersWithViews().AddJsonOptions(opts => {
        opts.JsonSerializerOptions.PropertyNameCaseInsensitive= true;
        opts.JsonSerializerOptions.Converters.Insert(0, new AutoNumberToStringConverter());
    });
    
    Run Code Online (Sandbox Code Playgroud)

    然后 MVC/Razor 将自动处理类型转换。

  3. 或者,如果您想手动控制序列化/反序列化:

    var opts = new JsonSerializerOptions {
        PropertyNameCaseInsensitive = true,
    };
    opts.Converters.Add(new AutoNumberToStringConverter());
    var o = JsonSerializer.Deserialize<Product>(json,opts) ;
    
    Run Code Online (Sandbox Code Playgroud)
  4. 以类似的方式,您可以启用字符串到数字类型的转换,如下所示:

    public class AutoStringToNumberConverter : JsonConverter<object>
    {
        public override bool CanConvert(Type typeToConvert)
        {
            // see https://stackoverflow.com/questions/1749966/c-sharp-how-to-determine-whether-a-type-is-a-number
            switch (Type.GetTypeCode(typeToConvert))
            {
                case TypeCode.Byte:
                case TypeCode.SByte:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.Decimal:
                case TypeCode.Double:
                case TypeCode.Single:
                return true;
                default:
                return false;
            }
        }
        public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if(reader.TokenType == JsonTokenType.String) {
                var s = reader.GetString() ;
                return int.TryParse(s,out var i) ? 
                    i :
                    (double.TryParse(s, out var d) ?
                        d :
                        throw new Exception($"unable to parse {s} to number")
                    );
            }
            if(reader.TokenType == JsonTokenType.Number) {
                return reader.TryGetInt64(out long l) ?
                    l:
                    reader.GetDouble();
            }
            using(JsonDocument document = JsonDocument.ParseValue(ref reader)){
                throw new Exception($"unable to parse {document.RootElement.ToString()} to number");
            }
        }
    
    
        public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
        {
            var str = value.ToString();             // I don't want to write int/decimal/double/...  for each case, so I just convert it to string . You might want to replace it with strong type version.
            if(int.TryParse(str, out var i)){
                writer.WriteNumberValue(i);
            }
            else if(double.TryParse(str, out var d)){
                writer.WriteNumberValue(d);
            }
            else{
                throw new Exception($"unable to parse {str} to number");
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

  • 您应该在不变区域设置而不是当前区域性区域设置中序列化和解析数字,例如 `int.TryParse(s,NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var i) ` 和 `double.TryParse(s, NumberStyles.Float, NumberFormatInfo) .InvariantInfo,out var d)` (5认同)
  • 编辑后的答案不正确。属性字段必须是数字才能使用“AllowReadingFromString”,这与问题所问的相反。 (3认同)
  • (1) 中的转换器不应该是 JsonConverter&lt;string&gt; 而不是 JsonConverter&lt;object&gt; 吗?当前实现抛出 `System.InvalidCastException: 无法将类型 'System.Collections.Generic.List`1[System.String]' 的对象转换为类型 'System.Collections.Generic.IList`1[System.Object]'`将 [12345] 等数组反序列化为 string[] 字段时。此外,您也不需要重写 CanConvert() 方法。 (2认同)

Svj*_*Man 13

您可以在模型类中使用JsonNumberHandlingAttribute来指定如何处理数字反序列化。允许的选项在JsonNumberHandling枚举中指定。

使用示例:

public class Product
{
    [JsonNumberHandling(JsonNumberHandling.WriteAsString)]
    public string Id { get; set; }
    
    public string Name { get; set; }
}

Run Code Online (Sandbox Code Playgroud)

如果需要序列化 ​​from stringto int,可以使用JsonNumberHandling.AllowReadingFromString

  • 嗯,当我像这样放置它时,我收到“当'JsonNumberHandlingAttribute'放置在属性或字段上时,该属性或字段必须是数字或集合”错误 (14认同)
  • 是的,我认为这仅在您想要将字符串反序列化为数字而不是将数字反序列化为字符串时才有效。如果您查看迁移指南,不幸的是,听起来 System.Text.Json 本身并不支持此功能,即使在 .NET 6 上也是如此:https://learn.microsoft.com/en-us/dotnet/standard/serialization/ system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-6-0#non-string-values-for-string-properties (8认同)
  • 应该注意的是,这仅适用于将“System.Text.Json”与 .NET 5.0 或 .NET 6.0 Preview 7(截至 2021 年 8 月)框架版本一起使用。请参阅答案中引用“JsonNumberHandlingAttribute”的链接,特别是“适用于”部分。 (5认同)

mar*_*sze 12

在选项中,将NumberHandling属性设置为AllowReadingFromString

var o = JsonSerializer.Deserialize<Product>(json, new JsonSerializerOptions
{
    // [...]
    NumberHandling = JsonNumberHandling.AllowReadingFromString
});
Run Code Online (Sandbox Code Playgroud)

  • Json 数据就像 { "No": 2 } ,类型是记录数据(字符串否),它无法反序列化它。现在我明白AllowReadingFromString期望像{“No”:“2”}这样的数据,我的意思是数字作为字符串......所以这是一个不同的情况。 (2认同)
  • @Freshblood该选项是从*字符串读取(将字符串反序列化为数字),而不是反序列化*为*字符串。不知道该怎么做。 (2认同)
  • @Freshblood 应该注意的是,这仅适用于将“System.Text.Json”与 .NET 5.0 或 .NET 6.0 Preview 7(截至 2021 年 8 月)框架版本一起使用。请参阅答案中引用“NumberHandling”的链接,特别是“适用于”部分。 (2认同)