繁体   English   中英

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

[英]Newtonsoft.Json - Deserialize uppercase boolean values without quotes

我从api收到一些不是真正符合ISO标准的Json内容。 布尔值是大写而不是小写。

{ "Bool": False }

最初,我认为应该使用自定义JsonConverter来解决,如如何使newtonsoft将yes和no反序列化为boolean所示

但是看起来好像从未调用过JsonConverter.ReadJson方法。 我认为原因是值False不在引号中,因此JsonTextReader从未调用转换器并创建异常。

处理这种情况的最佳方法是什么?

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/5924981
public 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();
    }
}

正如拉瑟所说:

无效的json应该在源头修复。

如果您确实需要按原样解析它,则可以将False替换为False(如@Sinatr所建议),如果您希望将其作为字符串,则将其替换为false。

// If you want a string
json.Replace("False", "\"False\"");

// If you want a bool
json.Replace("False", "false");

一个问题是键或另一个值是否包含“ False”模式。

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

为反序列化器使用转换器和策略设置将无法正常工作,因为它们打算处理诸如空对象返回数组或名称转换/大小写处理之类的事情。

一个幼稚的解决方案是做一个简单的字符串替换,例如

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

但是,这有一些问题:

  1. 您需要将整个无效的json读取到内存中,并创建它的固定副本,这意味着您将在内存中拥有两个完整的数据副本,一个坏的副本,一个更好的副本。
  2. 它将替换字符串内部的False 这可能对您的数据不是问题,但使用上述方法并不容易。

一种不同的方法是编写一个基本的令牌解析器,该令牌解析器可以理解基本的JSON语法(例如字符串,数字和标识符),并逐个令牌地遍历文件令牌,从而替换错误的标识符。 这样可以解决问题2,但是根据解决方案的不同,可能需要更复杂的实现来解决内存中的问题1。

以下是创建可使用的TextReader简单尝试,该方法将在找到标识符时对其进行修复,否则将理解基本的JSON令牌。

请注意以下几点:

  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
}

输出(来自LINQPad ):

样本LINQPad输出

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM