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

Automatic conversion of numbers to bools - migrating from Newtonsoft to System.Text.Json

我在我的 ASP.NET 核心应用程序中切换到 System.Text.Json,不小心在我的 API 中引入了重大更改。我有一个客户正在发送 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 fiddle:https://dotnetfiddle.net/xgm2u7

您可以创建自定义 JsonConverter<bool> that emulates the logic of Json.NET's 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);

演示 fiddle #1 here.

备注:

  • Json.NET 自动尝试使用 bool.TryParse() which parses using a comparison that is ordinal and case-insensitive. Simply checking e.g. Utf8JsonReader.ValueTextEquals("true") 将字符串反序列化为 bool 不会模拟 Newtonsoft 的大小写不变性。

  • Json.NET 通过尝试将值标记解析为 longdouble(或 decimal 当结果 FloatParseHandling.Decimal is set) and then calling Convert.ToBoolean() 时。转换器模拟此逻辑。

  • 可能存在一些边界条件,我的 BoolConverter<bool> 和 Json.NET 的行为并不相同,特别是在溢出或接近零的情况下。

  • 如果您还需要支持将数字反序列化为 bool?,请从 to 中获取 NullableConverterFactory 并将其添加到转换器中,如下所示:

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

    此转换器工厂还修复了从 Newtonsoft 到 System.Text.Json 的另一个模糊的重大更改,即前者会将空 JSON 字符串 "" 反序列化为任何 Nullable<T> 但是后者不会。

    演示 fiddle #2 here.