自动将数字转换为布尔值 - 从Newtonsoft迁移到System.Text.Json

13
我在我的ASP.NET Core应用中切换到System.Text.Json时,不小心引入了一个破坏性变化。我有一个客户端正在发送JSON文档,并且在布尔字段上使用数字10,而不是truefalse
// What they're sending.
{ "Active": 1 }

// What they should be sending.
{ "Active": true }
Newtonsoft.Json 库会自动将数字转换为布尔值(0 = false,其他值 = true),但是 System.Text.Json 不会这样做;它会抛出异常。这意味着我的 API 端点突然对发送 10 的愚蠢客户端不再起作用!
我似乎在迁移指南中找不到任何关于此的提及。我想将行为恢复为 Newtonsoft 的处理方式,但我不确定是否有标志可以启用它,或者我是否需要编写自定义转换器。
能否有人帮我将行为恢复为与 Newtonsoft 相同?
以下是一些代码以演示问题:
using System;

string data = "{ \"Active\": 1 }";

try
{
    OutputState s1 = System.Text.Json.JsonSerializer.Deserialize<OutputState>(data);
    Console.WriteLine($"OutputState 1: {s1.Active}");
}
catch (Exception ex)
{
    Console.WriteLine($"System.Text.Json failed: {ex.Message}");
}

try
{
    OutputState s2 = Newtonsoft.Json.JsonConvert.DeserializeObject<OutputState>(data);
    Console.WriteLine($"OutputState 2: {s2.Active}");
}
catch (Exception ex)
{
    Console.WriteLine($"Newtonsoft.Json failed: {ex.Message}");
}

public record OutputState(bool Active);

另外还有一个.NET的调试工具,可供互动式演示:https://dotnetfiddle.net/xgm2u7

在您的示例中,似乎System.Text.Json不允许使用记录:System.Text.Json失败:不支持没有无参构造函数的引用类型的反序列化。类型“OutputState”(但它可以使用类)。 - Métoule
2
可以实现自定义转换器。你尝试过这种方法吗?https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to?pivots=dotnet-5-0 - Noah Stahl
2
@Métoule 在 .NET 5 中是可能的。你可能在使用 3.1 或更低版本:https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-immutability?pivots=dotnet-5-0#immutable-types-and-records - Phil K
1个回答

24

您可以创建一个自定义的JsonConverter<bool>,模拟Json.NET的JsonReader.ReadAsBoolean()逻辑:

public class BoolConverter : JsonConverter<bool>
{
    public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) =>
        writer.WriteBooleanValue(value);
    
    public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
        reader.TokenType switch
        {
            JsonTokenType.True => true,
            JsonTokenType.False => false,
            JsonTokenType.String => bool.TryParse(reader.GetString(), out var b) ? b : throw new JsonException(),
            JsonTokenType.Number => reader.TryGetInt64(out long l) ? Convert.ToBoolean(l) : reader.TryGetDouble(out double d) ? Convert.ToBoolean(d) : false,
            _ => throw new JsonException(),
        };
}

将其添加到 JsonSerializerOptions.Converters 中以使用:

var options = new JsonSerializerOptions
{
    Converters = { new BoolConverter() },
};
var s1 = System.Text.Json.JsonSerializer.Deserialize<OutputState>(data, options);

演示代码 #1 在这里

注:

  • Json.NET automatically tries to deserialize strings to bool using bool.TryParse() which parses using a comparison that is ordinal and case-insensitive. Simply checking e.g. Utf8JsonReader.ValueTextEquals("true") will not emulate Newtonsoft's case invariance.

  • Json.NET supports deserializing both integers and floating point numbers to bool by attempting to parse the value token to a long or double (or decimal when FloatParseHandling.Decimal is set) and then calling Convert.ToBoolean() on the result. The converter emulates this logic.

  • There may be some boundary conditions where my BoolConverter<bool> and Json.NET do not behave identically, particularly for overflow or rounding-near-zero situations.

  • If you also need to support deserializing numbers to bool?, grab NullableConverterFactory from this answer to How to deserialize an empty string to a null value for all `Nullable<T>` value types using System.Text.Json? and add it to converters like so:

     var options = new JsonSerializerOptions
     {
         Converters = { new BoolConverter(), new NullableConverterFactory() },
     };
    

    This converter factory also fixes another obscure breaking change from Newtonsoft to System.Text.Json, namely that the former will deserialize an empty JSON string "" to any Nullable<T> but the latter will not.

    Demo fiddle #2 here.


4
或者,你可以使用属性指定转换器来反序列化特定的属性:[JsonConverter(typeof(BoolConverter))]bool Active { get;set; }。然后像往常一样调用Deserialize - n0rd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接