为了账号安全,请及时绑定邮箱和手机立即绑定

Newtonsoft.Json - 反序列化不带引号的大写布尔值

Newtonsoft.Json - 反序列化不带引号的大写布尔值

C#
开心每一天1111 2023-09-16 20:03:38
我从 API 收到一些不太符合 ISO 标准的 Json 内容。布尔值为大写而不是小写。{ "Bool": False }最初,我认为通过使用自定义应该很容易解决,例如如何让 newtonsoft 将 yes 和 no 反序列化为布尔值。JsonConverter但看起来该方法从未被调用过。我认为原因是,该值不在引号中,因此永远不会调用转换器并创建异常。JsonConverter.ReadJsonFalseJsonTextReader处理这种情况的最佳方法是什么?public class BoolTests{    public class A    {        [JsonConverter(typeof(CaseIgnoringBooleanConverter))]        public bool Bool { get; set; }    }    [Theory]    [InlineData(false, "{'Bool': false}")] //ok    [InlineData(false, "{'Bool': 'False'}")] // ok    [InlineData(false, "{'Bool': False")] // fails    public void CasingMatters(bool expected, string json)    {        var actual = JsonConvert.DeserializeObject<A>(json);        Assert.Equal(expected, actual.Bool);    }}// taken from https://gist.github.com/randyburden/5924981public class CaseIgnoringBooleanConverter : JsonConverter{    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)    {        switch (reader.Value.ToString().ToUpperInvariant().Trim())        {            case "TRUE":                return true;            case "FALSE":                return false;        }        // If we reach here, we're pretty much going to throw an error so let's let Json.NET throw it's pretty-fied error message.        return new JsonSerializer().Deserialize(reader, objectType);    }    public override bool CanConvert(Type objectType)    {        return objectType == typeof(bool);    }    public override bool CanWrite => false;    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)    {        throw new NotImplementedException();    }}
查看完整描述

2 回答

?
30秒到达战场

TA贡献1828条经验 获得超6个赞

不幸的是,正如您所发现的,无效的 json 是无效的,因此无法由普通和常见的 json (de) 序列化程序(如 Json.net)处理。

对反序列化程序使用转换器和策略设置也不起作用,因为它们旨在处理诸如空对象作为数组返回或名称转换/大小写处理之类的事情。

一个天真的解决方案是做一个简单的字符串替换,比如

string json = invalidJson.Replace("False", "false");

但是,这存在一些问题:

  1. 您需要将整个无效的 json 读入内存,并创建它的固定副本,这意味着您将在内存中拥有两个完整的数据副本,一个是坏的,一个是好的。

  2. 它也会替换内部字符串。这可能不是您的数据问题,但使用上述方法并不容易处理。False

另一种方法是编写一个基本的分词器,该分词器可以理解基本的 JSON 语法,例如字符串、数字和标识符,并逐个令牌遍历文件,替换错误的标识符。这将解决问题 2,但根据解决方案,可能需要更复杂的实现来修复内存问题 1。

下面发布了一个简单的尝试,创建一个可以使用的标识符,这将在找到标识符时修复它们,否则可以理解基本的 JSON 令牌。TextReader

请注意以下几点:

  1. 它不是真正的性能。它始终分配临时缓冲区。您可能希望研究“缓冲区租用”以更好地处理此方法,甚至只是直接流式传输到缓冲区。

  2. 它不处理数字,因为那时我停止编写代码。我把这个留作练习。可以编写基本的数字处理,因为您并没有真正验证文件是否具有有效的 JSON,因此可以添加任何可以获取足够字符来构成数字的内容。

  3. 我没有用非常大的文件对此进行测试,只用小的示例文件进行测试。我复制了一个9.5MB的文本,它适用于此。List<Test>

  4. 我没有测试所有 JSON 语法。可能存在应该处理但未处理的字符。如果您最终使用它,请创建大量测试!

但是,它的作用是根据您发布的标识符修复无效的 JSON,并且它以流式方式执行此操作。因此,无论您的 JSON 文件有多大,这都应该可用。

无论如何,这是代码,再次注意有关数字的异常:

void Main()

{

    using (var file = File.OpenText(@"d:\temp\test.json"))

    using (var fix = new MyFalseFixingTextReader(file))

    {

        var reader = new JsonTextReader(fix);

        var serializer = new JsonSerializer();

        serializer.Deserialize<Test>(reader).Dump();

    }

}


public class MyFalseFixingTextReader : TextReader

{

    private readonly TextReader _Reader;

    private readonly StringBuilder _Buffer = new StringBuilder(32768);


    public MyFalseFixingTextReader(TextReader reader) => _Reader = reader;


    public override void Close()

    {

        _Reader.Close();

        base.Close();

    }


    public override int Read(char[] buffer, int index, int count)

    {

        TryFillBuffer(count);


        int amountToCopy = Math.Min(_Buffer.Length, count);

        _Buffer.CopyTo(0, buffer, index, amountToCopy);

        _Buffer.Remove(0, amountToCopy);

        return amountToCopy;

    }


    private (bool more, char c) TryReadChar()

    {

        int i = _Reader.Read();

        if (i < 0)

            return (false, default);

        return (true, (char)i);

    }


    private (bool more, char c) TryPeekChar()

    {

        int i = _Reader.Peek();

        if (i < 0)

            return (false, default);

        return (true, (char)i);

    }


    private void TryFillBuffer(int count)

    {

        if (_Buffer.Length >= count)

            return;


        while (_Buffer.Length < count)

        {

            var (more, c) = TryPeekChar();

            if (!more)

                break;

            switch (c)

            {

                case '{':

                case '}':

                case '[':

                case ']':

                case '\r':

                case '\n':

                case ' ':

                case '\t':

                case ':':

                case ',':

                    _Reader.Read();

                    _Buffer.Append(c);

                    break;


                case '"':

                    _Buffer.Append(GrabString());

                    break;


                case char letter when char.IsLetter(letter):

                    var identifier = GrabIdentifier();

                    _Buffer.Append(ReplaceFaultyIdentifiers(identifier));

                    break;


                case char startOfNumber when startOfNumber == '-' || (startOfNumber >= '0' && startOfNumber <= '9'):

                    _Buffer.Append(GrabNumber());

                    break;


                default:

                    throw new InvalidOperationException($"Unable to cope with character '{c}' (0x{((int)c).ToString("x2")})");

            }

        }

    }


    private string ReplaceFaultyIdentifiers(string identifier)

    {

        switch (identifier)

        {

            case "False":

                return "false";


            case "True":

                return "true";


            case "Null":

                return "null";


            default:

                return identifier;

        }

    }


    private string GrabNumber()

    {

        throw new NotImplementedException("Left as an excercise");

        // See https://www.json.org/ for the syntax

    }


    private string GrabIdentifier()

    {

        var result = new StringBuilder();

        while (true)

        {

            int i = _Reader.Peek();

            if (i < 0)

                break;


            char c = (char)i;

            if (char.IsLetter(c))

            {

                _Reader.Read();

                result.Append(c);

            }

            else

                break;

        }

        return result.ToString();

    }


    private string GrabString()

    {

        _Reader.Read();


        var result = new StringBuilder();

        result.Append('"');


        while (true)

        {

            var (more, c) = TryReadChar();

            if (!more)

                return result.ToString();


            switch (c)

            {

                case '"':

                    result.Append(c);

                    return result.ToString();


                case '\\':

                    result.Append(c);

                    (more, c) = TryReadChar();

                    if (!more)

                        return result.ToString();


                    switch (c)

                    {

                        case 'u':

                            result.Append(c);

                            for (int index = 1; index <= 4; index++)

                            {

                                (more, c) = TryReadChar();

                                if (!more)

                                    return result.ToString();

                                result.Append(c);

                            }

                            break;


                        default:

                            result.Append(c);

                            break;

                    }

                    break;


                default:

                    result.Append(c);

                    break;

            }

        }

    }

}


public class Test

{

    public bool False1 { get; set; }

    public bool False2 { get; set; }

    public bool False3 { get; set; }

}

示例文件:


{

    "false1": false,

    "false2": "false",

    "false3": False

}

输出:

https://img1.sycdn.imooc.com/650599f60001190c00990093.jpg

查看完整回答
反对 回复 2023-09-16
?
蓝山帝景

TA贡献1843条经验 获得超7个赞

应在源中修复无效的 json。


如果你真的需要按原样解析它,如果你想把它作为一个字符串,你可以用“False”替换 False,如果你想把它作为一个布尔值,你可以用 false 替换它。


// If you want a string

json.Replace("False", "\"False\"");


// If you want a bool

json.Replace("False", "false");

一个问题是,如果一个键或其他值包含“False”模式。


查看完整回答
反对 回复 2023-09-16
  • 2 回答
  • 0 关注
  • 172 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信