Newtonsoft.JSON无法使用TypeConverter属性转换模型

Ste*_*eve 13 c# serialization json typeconverter json.net

我有一个C#MVC应用程序,它将数据作为JSON字符串存储在XML文档中,也存储在MySQL数据库表中.

最近我收到了在MySQL数据库字段中存储JSON字符串的要求,要通过Newtonsoft.Json转换为C#对象,所以我决定实现一个TypeConverter将JSON字符串转换为自定义C#模型.

不幸的是,当TypeConverter属性添加到我的C#Model时,我无法在我的解决方案中的任何地方使用以下命令来反序列化我的JSON字符串:

JsonConvert.DeserializeObject<Foo>(json);
Run Code Online (Sandbox Code Playgroud)

删除属性可以解决问题但是这会阻止我将MySQL DB字段转换为自定义C#对象.

这是我添加了TypeConverter属性的C#模型:

using System.ComponentModel;

[TypeConverter(typeof(FooConverter))]
public class Foo
{
    public bool a { get; set; }
    public bool b { get; set; }
    public bool c { get; set; }
    public Foo(){}
}
Run Code Online (Sandbox Code Playgroud)

这是我的TypeConverter类:

using Newtonsoft.Json;
using System;
using System.ComponentModel;

    public class FooConverter : TypeConverter
    {
        public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
        {
            if (sourceType == typeof(string))
            {
                return true;
            }
            return base.CanConvertFrom(context, sourceType);
        }
        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            if (value is string)
            {
                string s = value.ToString().Replace("\\","");
                Foo f = JsonConvert.DeserializeObject<Foo>(s);
                return f;
            }
            return base.ConvertFrom(context, culture, value);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

一旦我将属性添加到Foo类,我收到以下错误:

无法将当前JSON对象(例如{"name":"value"})反序列化为类型"Models.Foo",因为该类型需要JSON字符串值才能正确反序列化.

要修复此错误,请将JSON更改为JSON字符串值或更改反序列化类型,使其成为正常的.NET类型(例如,不是像整数这样的基本类型,而不是像数组或列表那样的集合类型),可以反序列化来自JSON对象.JsonObjectAttribute也可以添加到类型中以强制它从JSON对象反序列化.

我使用以下字符串(在没有添加TypeConverter属性的情况下完美运行):

"{\"Foo\":{\"a\":true,\"b\":false,\"c\":false}}"
Run Code Online (Sandbox Code Playgroud)

不知道这里发生了什么,有什么想法吗?

非常感谢!!!

UPDATE

我已经发现我对MVC API控制器上的操作有问题,这些操作接受带有Foo作为属性的Test Class,或者在将TypeConverter属性添加到Foo类时接受Foo作为对象的控制器上.

以下是具有问题的测试控制器的示例:

public class TestController : ApiController
{
    [AcceptVerbs("POST", "GET")]
    public void PostTestClass(TestClass t)
    {
        // Returns null when TypeConverter attribute is added to the Foo Class
        return t.Foo; 
    }
    AcceptVerbs("POST", "GET")]
    public void PostFooObj(Foo f)
    {
        // Returns null when TypeConverter attribute is added to the Foo Class
        return f;
    }
}
Run Code Online (Sandbox Code Playgroud)

TypeConverter可能导致覆盖WebAPI模型绑定的问题,并且当上述任一操作通过AJAX通过以下结构接收JSON时返回null:

// eg. PostTestClass(TestClass T)
{'Foo': {'a': false,'b': true,'c': false}};

// eg. PostFooObj(Foo f)
{'a': false,'b': true,'c': false}
Run Code Online (Sandbox Code Playgroud)

将TypeConverter属性添加到Foo类时,只要找到路径,就会调用FooConverter TypeConverter类上的以下方法:

    public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }
Run Code Online (Sandbox Code Playgroud)

FooConverter TypeController上的ConvertFrom方法不是由ApiController的动作调用的,这可能是问题的原因.

同样,这是类似的情况,其中控制器操作将在没有TypeConverter属性的情况下正常工作.

非常感谢!!

非常感谢.

dbc*_*dbc 22

这里有一些事情发生.首先,一个初步问题:即使没有TypeConverter应用,你的JSON也不对应你的类Foo,它对应于一些包含Foo属性的容器类,例如:

public class TestClass
{
    public Foo Foo { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

即给出您的JSON字符串,以下将不起作用:

var json = "{\"Foo\":{\"a\":true,\"b\":false,\"c\":false}}";
var foo = JsonConvert.DeserializeObject<Foo>(json);
Run Code Online (Sandbox Code Playgroud)

但以下将:

var test = JsonConvert.DeserializeObject<TestClass>(json);
Run Code Online (Sandbox Code Playgroud)

我怀疑这只是问题中的一个错误,所以我假设您正在寻找反序列化包含属性的类Foo.

您看到的主要问题是Json.NET 将尝试使用TypeConverterif if来将要序列化的类转换为JSON字符串.来自文档:

原始类型

.Net :( TypeConverter可转换为String)
JSON:String

但是在您的JSON中,Foo它不是JSON字符串,而是JSON 对象,因此一旦应用了类型转换器,反序列化就会失败.嵌入的字符串如下所示:

{"Foo":"{\"a\":true,\"b\":false,\"c\":false}"}
Run Code Online (Sandbox Code Playgroud)

注意所有报价是如何被转义的.即使您更改了Foo对象的JSON格式以匹配它,您的反序列化仍然会失败,因为TypeConverter和Json.NET尝试递归地相互调用.

因此,您需要做的是全局禁用TypeConverterJson.NET的使用并回退到默认序列化,同时保留TypeConverter在所有其他情况下的使用.这有点棘手,因为没有Json.NET属性可以应用于禁用类型转换器,而是需要一个特殊的合同解析器以及一个特殊的JsonConverter使用它:

public class NoTypeConverterJsonConverter<T> : JsonConverter
{
    static readonly IContractResolver resolver = new NoTypeConverterContractResolver();

    class NoTypeConverterContractResolver : DefaultContractResolver
    {
        protected override JsonContract CreateContract(Type objectType)
        {
            if (typeof(T).IsAssignableFrom(objectType))
            {
                var contract = this.CreateObjectContract(objectType);
                contract.Converter = null; // Also null out the converter to prevent infinite recursion.
                return contract;
            }
            return base.CreateContract(objectType);
        }
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Deserialize(reader, objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JsonSerializer.CreateDefault(new JsonSerializerSettings { ContractResolver = resolver }).Serialize(writer, value);
    }
}
Run Code Online (Sandbox Code Playgroud)

并使用它像:

[TypeConverter(typeof(FooConverter))]
[JsonConverter(typeof(NoTypeConverterJsonConverter<Foo>))]
public class Foo
{
    public bool a { get; set; }
    public bool b { get; set; }
    public bool c { get; set; }
    public Foo() { }
}

public class FooConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }
    public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    {
        if (value is string)
        {
            string s = value.ToString();
            //s = s.Replace("\\", "");
            Foo f = JsonConvert.DeserializeObject<Foo>(s);
            return f;
        }
        return base.ConvertFrom(context, culture, value);
    }
}
Run Code Online (Sandbox Code Playgroud)

示例小提琴.

最后,您可能还应该ConvertTo在类型转换器中实现该方法,请参见如何:实现类型转换器.

  • 我刚刚遇到过这个问题,感谢您的参考,我通过添加CanConvertTo解决了这个问题,如果目标类型是String,则返回false.没有必要使用NoTypeConverterJsonConverter.当Foo作为属性时,它也没有WebApi Controller的问题. (2认同)
  • 解决方案对我来说效果很好,所以谢谢!但是我认为这是Json.net的设计问题。如果我有一个实际上应该(应该)从字符串转换为JSON序列化的类怎么办?还是有问题的类是在外部(第三方)库中定义的(并且被赋予了属性),我无法更改它,但是我确实想在使用的应用程序中对其进行序列化? (2认同)