如何使用Newtonsoft将“是”和“否”反序列化为布尔值。

19

注意:我已经在本文底部提供了解决方案。

我有一个C# Win 8应用程序,其中我正在反序列化一些看起来像这样的json:

{
    'Unit': [
        {
            'name':'House 123',
            isAvailable:'no'
        },
        {
            'name':'House 456',
            isAvailable:'yes'
        }]
}

转换为使用此接口的类:

public interface IUnit
{
    string Name { get; }
    bool isAvailable { get; }
}

但是Newtonsoft抛出了一个错误:

解析值时遇到意外字符:n。路径'Unit [0] .isAvailable,第1行,第42位。

有没有办法扩展Newtonsoft,使其根据bool类型的结果对象属性解析yes / no或1/0?现在它仅适用于true / false。

有几篇关于自定义类转换器的文章,但没有关于bool这样的基本类型。

有什么建议吗?

5个回答

25
public class MyBooleanConverter : JsonConverter
{
    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var value = reader.Value;

        if (value == null || String.IsNullOrWhiteSpace(value.ToString()))
        {
            return false;
        }

        if ("yes".Equals(value, StringComparison.OrdinalIgnoreCase))
        {
            return true;
        }

        return false;
    }

    public override bool CanConvert(Type objectType)
    {
        if (objectType == typeof(String) || objectType == typeof(Boolean))
        {
            return true;
        }
        return false;
    }
}


public interface IUnit
{
    string Name { get; }

    [JsonConverter(typeof(MyBooleanConverter))]
    bool isAvailable { get; }
}

谢谢您的快速回复,Craig。这比我想出来的解决方案更好,但是我在将其与我的代码配合使用时遇到了问题:我已经在下一个答案中发布了它... - CodeChops
Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer(); serializer.Converters.Add(new MyBooleanConverter()); string json = "{'Unit':[{'name':'Apartment 123',isSingleUnit:'no'},{'name':'House 456',isSingleUnit:'yes'}]}".Replace(''', '"'); var obj = serializer.Deserialize(new StringReader(json), typeof(bool)); Console.WriteLine(obj); 它只返回“false”。 - CodeChops
1
基础部分做得不错,但你的代码可以大幅简化 :) - Sten Petrov
考虑一下这个:https://dev59.com/O3RA5IYBdhLWcg3w0xnk#809558 然后考虑将比较改为:"if ("yes".Equals(value.ToString() ..." - Wesley Long

13

我建议采用这种方法

using System;
using Newtonsoft.Json;

namespace JsonConverters
{
    public class BooleanJsonConverter : JsonConverter
    {
        public override bool CanConvert( Type objectType )
        {
            return objectType == typeof( bool );
        }

        public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer )
        {
            switch ( reader.Value.ToString().ToLower().Trim() )
            {
                case "true":
                case "yes":
                case "y":
                case "1":
                    return true;
                case "false":
                case "no":
                case "n":
                case "0":
                    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 void WriteJson( JsonWriter writer, object value, JsonSerializer serializer )
        {
        }

    }
}

6

这是我想到的内容。

public class JsonBooleanConverter : JsonConverter
{
    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var value = reader.Value.ToString().ToLower().Trim();
        switch (value)
        {
            case "true":
            case "yes":
            case "y":
            case "1":
                return true;
        }
        return false;
    }

    public override bool CanConvert(Type objectType)
    {
        if (objectType == typeof(Boolean))
        {
            return true;
        }
        return false;
    }
}

使用方法:

var myObj = JsonConvert.DeserializeObject<T>(json, new JsonBooleanConverter());

这是有效的。https://github.com/petekapakos/JsonBooleanConverterTest - Pete
2
是的,对于您特定和精心制作的测试用例而言是这样。试试这个:将main()函数中的所有内容替换为两个调用:JsonConvert.DeserializeObject<bool>("true", new JsonBooleanConverter()); JsonConvert.DeserializeObject<bool>("yes", new JsonBooleanConverter());。第一个将成功,后者将失败,并出现以下异常: {"Unexpected character encountered while parsing value: y. Path '', line 0, position 0."}。这是因为您误用了转换器:它不是用来预处理格式不正确的 JSON 的机制,这是其被当做的方式。 - rpj

1

//这是我想出来的...

   using System;
 using System.Collections.Generic;
 using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace NewtonTest
{

internal class NewtonTest
{
    public class Data
    {
        public IEnumerable<IUnit> Unit { get; set; }

        public override string ToString()
        {
            return string.Format("Data{{Unit=[{0}]}}",
                string.Join(", ", Unit.Select(c =>
                                string.Format("{0} - Single Unit: {1}", 
                                    c.Name,
                                    c.isSingleUnit.ToString()))));
        }
    }

    public interface IUnit
    {
        string Name { get; }

        // [JsonConverter(typeof(Converter))]
        bool isSingleUnit { get; }
    }

    public class House : IUnit
    {
        public House(string name, bool isSingle)
        {
            this.Name = name;
            this.isSingleUnit = isSingle;
        }

        public string Name { get; private set; }

        public bool isSingleUnit { get; private set; }
    }

    public class Apartment : IUnit
    {
        public Apartment(string name, bool isSingle)
        {
            this.Name = name;
            this.isSingleUnit = isSingle;
        }

        public string Name { get; private set; }

        public bool isSingleUnit { get; private set; }
    }

    private static bool ConvertToBool(string value)
    {
        value =
            value.ToUpper().
                  Replace("YES", "TRUE").
                  Replace("Y", "TRUE").
                  Replace("1", "TRUE").
                  Replace("NO", "FALSE").
                  Replace("N", "FALSE").
                  Replace("0", "FALSE");

        bool result = false;

        bool.TryParse(value, out result);

        return result;
    }


    private class UnitConverter : Newtonsoft.Json.JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof (NewtonTest.IUnit).IsAssignableFrom(objectType);
        }

        public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue,
                                        Newtonsoft.Json.JsonSerializer serializer)
        {
            JObject obj = serializer.Deserialize<JToken>(reader) as JObject;

            if (obj != null)
            {
                string result = obj["isSingleUnit"].ToObject<string>();

                bool isSingleUnit = ConvertToBool(result);

                string name = obj["name"].ToObject<string>();

                if (isSingleUnit)
                {
                    return new NewtonTest.House(name, isSingleUnit);
                }
                else
                {
                    return new NewtonTest.Apartment(name, isSingleUnit);
                }
            }
            else
            {
                return null;
            }
        }

        public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value,
                                       Newtonsoft.Json.JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }


    public static void Main()
    {
        Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
        serializer.Converters.Add(new UnitConverter());
        string json =
            "{'Unit':[{'name':'Apartment 123',isSingleUnit:'no'},{'name':'House 456',isSingleUnit:'yes'}]}".Replace(
                '\'', '\"');
        var obj = serializer.Deserialize(new StringReader(json), typeof (Data));
        Console.WriteLine(obj);
        Console.ReadKey();
    }
}
}

我没有看到你告诉反序列化程序你期望一个 IUnit。它不会知道应该应用正确的转换器。 - Craig Stuntz
抱歉,代码嵌套方式不易阅读。它在这个方法中:public override bool CanConvert(Type objectType) { return typeof (NewtonTest.IUnit).IsAssignableFrom(objectType); } - CodeChops
不够。你需要在 Main() 中引用 IUnit - Craig Stuntz

0
这是@John的解决方案的VB版本,适用于需要的任何人。它处理布尔值和可空布尔值。在写入时,它将转换为0/1以节省传输中的一些字节(而不是true/false):
Imports Newtonsoft.Json

Public Class MyBooleanConverter
    Inherits JsonConverter

Public Overrides ReadOnly Property CanWrite As Boolean
    Get
        Return True
    End Get
End Property

Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
    Dim boolVal As Boolean = value
    writer.WriteValue(If(boolVal, 1, 0))
End Sub

Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
    Dim value = reader.Value
    If IsNothing(value) OrElse String.IsNullOrWhiteSpace(value.ToString()) OrElse "0" = value Then
        Return False
    End If
    If 0 = String.Compare("yes", value, True) OrElse 0 = String.Compare("true", value, True) Then
        Return True
    End If
    Return False
End Function

Public Overrides Function CanConvert(objectType As Type) As Boolean
    Return objectType = GetType(Boolean) OrElse objectType = GetType(Boolean?) 'OrElse objectType = GetType(String)
End Function
End Class

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