Newtonsoft.JSON不能转换模型的TypeConverter属性 [英] Newtonsoft.JSON cannot convert model with TypeConverter attribute

查看:2081
本文介绍了Newtonsoft.JSON不能转换模型的TypeConverter属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 C#MVC 应用程序,数据存储为XML文档中的JSON字符串以及在MySQL数据库表。

最近,我已经收到了要求在MySQL数据库领域,通过Newtonsoft.Json 转换成 C#对象,所以我决定实现的TypeConverter店JSON字符串以JSON字符串转换成定制的C#模式。

可惜我不能在任何地方使用下面的命令在我的解决方案,我反序列化JSON字符串时,在的TypeConverter属性添加到我的C#型号

  JsonConvert.DeserializeObject<富>(JSON);

删除属性解决问题然而,这prevents我从转换MySQL数据库的字段到自定义的C#对象。

下面是我的 C#型号的TypeConverter的属性补充说:

 使用System.ComponentModel;[的TypeConverter(typeof运算(FooConverter))]
公共类Foo
{
    公共BOOL一个{搞定;组; }
    公共布尔B {搞定;组; }
    公共BOOL 13C {搞定;组; }
    公共美孚(){}
}

下面是我的类型转换器类

 使用Newtonsoft.Json;
使用系统;
使用System.ComponentModel;    公共类FooConverter:类型转换器
    {
        公众覆盖布尔CanConvertFrom(ITypeDescriptorContext背景下,System.Type的sourceType的)
        {
            如果(sourceType的== typeof运算(字符串))
            {
                返回true;
            }
            返回base.CanConvertFrom(上下文,sourceType的);
        }
        公共覆盖对象ConvertFrom(ITypeDescriptorContext背景下,System.Globalization.CultureInfo文化,对象的值)
        {
            如果(值为字符串)
            {
                字符串s = value.ToString()更换(\\\\,​​);
                富F = JsonConvert.DeserializeObject<富>(S);
                返回F;
            }
            返回base.ConvertFrom(上下文,文化,价值);
        }
    }
}

只要我的属性添加到Foo类我收到以下错误:

无法反序列化当前的JSON对象(如{名:值})。因为类型需要一个JSON字符串值正确地反序列化为类型Models.Foo

要解决这个错误要么改变的JSON到一个JSON字符串值或更改反序列化的类型,以便它是一个正常的.NET类型(例如,不是原始类型类似整数,而不是一个集合类型像数组或列表),可以从JSON对象反序列化。 JsonObjectAttribute也可以加入到类型迫使它从JSON对象反序列化。

我使用下面的字符串(这完全工作没有添加的TypeConverter属性):

 {\\富\\:{\\A \\:真实的,\\B \\:假的,\\C:\\:假}}

不知道是怎么回事,什么想法?

非常感谢!

更新

我发现,我也有问题,对 MVC API控制器接受在测试类与美孚作为一个属性的行为或控制器上接受的美孚作为对象的TypeConverter属性添加到Foo类。

下面是测试控制器,其具有的问题的一个例子:

 公共类的TestController:ApiController
{
    的[AcceptVerbs(POST,GET)]
    公共无效PostTestClass(识别TestClass T)
    {
        //当TypeConverter的属性添加到Foo类,则返回null
        返回t.Foo;
    }
    AcceptVerbs(POST,GET)]
    公共无效PostFooObj(美孚F)
    {
        //当TypeConverter的属性添加到Foo类,则返回null
        返回F;
    }
}

该类型转换器可能导致覆盖的WebAPI模型绑定的问题,无论何时行动通过AJAX上述接收JSON结构如下返回null:

  //如。 PostTestClass(识别TestClass T)
{'富':{'A':假的,'B':真,'C':假}};//如。 PostFooObj(富F)
{A:假的,'B':真,'C':假}

当所述的TypeConverter属性被添加到富级,对FooConverter的TypeConverter类以下方法一旦路由发现称为:

 公共覆盖布尔CanConvertFrom(ITypeDescriptorContext背景下,System.Type的sourceType的)
    {
        如果(sourceType的== typeof运算(字符串))
        {
            返回true;
        }
        返回base.CanConvertFrom(上下文,sourceType的);
    }

在FooConverter TypeController的ConvertFrom方法不受ApiController的行动呼吁,这可能是问题的原因。

同样这是一个类似的情况,在控制器的动作将正常工作,没有TypeConverter的属性。

任何进一步的帮助很大AP preciated!

非常感谢。


解决方案

有几件事情怎么回事。首先,preliminary问题:即使没有的TypeConverter 应用,你的JSON不符合您的类 ,它对应于包含属性,比如一些容器类:

 公共类识别TestClass
{
    公共富富{搞定;组; }
}

即。鉴于你的JSON字符串,以下将不起作用:

  VAR的json ={\\富\\:{\\A \\:真实的,\\B \\:假的,\\C:\\:假}} ;
无功富= JsonConvert.DeserializeObject<富>(JSON);

不过,下列措施:

  VAR测试= JsonConvert.DeserializeObject<&的TestClass GT;(JSON);

我怀疑这根本就是问题的错误,所以我会假设你正在寻找反序列化类包含一个属性

您所看到的主要问题是,Json.NET的将尝试使用的TypeConverter 如果有一个present转换一个类被序列化一个JSON字符串的。从文档


  

基本类型


  
  

净:的TypeConverter (转换为字符串)结果
  JSON:字符串


但在你的JSON,不是一个JSON字符串,它是一个JSON的对象的,因而反序列化,一旦应用了类型转换失败。嵌入式字符串应该是这样的:


  {富:{\\A \\:真实的,\\B \\:假的,\\C:\\:虚假}


注意如何将所有的报价已经越狱。即使你改变了你的JSON格式对象来匹配这个,你的反序列化仍然会失败,因为的TypeConverter 键, Json.NET尝试递归调用对方。

因此​​,你需要做的是Json.NET在全球范围内禁止使用的TypeConverter ,并回落到默认的序列化,同时保留使用在所有其他情况下的TypeConverter 。这是一个有点棘手,因为没有 Json.NET属性可以应用到禁止使用类型转换器,而不是你需要一个特殊的合同解析器加上一个特殊的 JsonConverter 来使用它:

 公共类NoTypeConverterJsonConverter< T> :JsonConverter
{
    静态只读IContractResolver解析器=新NoTypeConverterContractResolver();    类NoTypeConverterContractResolver:DefaultContractResolver
    {
        保护覆盖JsonContract CreateContract(类型的objectType)
        {
            如果(typeof运算(T).IsAssignableFrom(的objectType))
            {
                VAR合同= this.CreateObjectContract(的objectType);
                contract.Converter = NULL; //还空出了转换器prevent无限递归。
                返回合同;
            }
            返回base.CreateContract(的objectType);
        }
    }    公众覆盖布尔CanConvert(类型的objectType)
    {
        返回的typeof(T).IsAssignableFrom(的objectType);
    }    公众覆盖对象ReadJson(JsonReader读者,类型的objectType,对象existingValue,JsonSerializer串行)
    {
        返回JsonSerializer.CreateDefault(新JsonSerializerSettings {ContractResolver =解析})反序列化(读者的objectType)。
    }    公共覆盖无效WriteJson(JsonWriter作家,对象的值,JsonSerializer串行)
    {
        JsonSerializer.CreateDefault(新JsonSerializerSettings {ContractResolver =解析})序列化(作家值)。
    }
}

和使用它像:

  [的TypeConverter(typeof运算(FooConverter))]
[JsonConverter(typeof运算(NoTypeConverterJsonConverter<富>))]
公共类Foo
{
    公共BOOL一个{搞定;组; }
    公共布尔B {搞定;组; }
    公共BOOL 13C {搞定;组; }
    公共美孚(){}
}公共类FooConverter:类型转换器
{
    公众覆盖布尔CanConvertFrom(ITypeDescriptorContext背景下,System.Type的sourceType的)
    {
        如果(sourceType的== typeof运算(字符串))
        {
            返回true;
        }
        返回base.CanConvertFrom(上下文,sourceType的);
    }
    公共覆盖对象ConvertFrom(ITypeDescriptorContext背景下,System.Globalization.CultureInfo文化,对象的值)
    {
        如果(值为字符串)
        {
            字符串s = value.ToString();
            // S = s.Replace(\\\\,​​);
            富F = JsonConvert.DeserializeObject<富>(S);
            返回F;
        }
        返回base.ConvertFrom(上下文,文化,价值);
    }
}

最后,你应该也实现你的类型转换器中的的ConvertTo 方法,看的How到:实现类型转换器

I have an C# MVC application which stores data as JSON strings in an XML document and also in MySQL DB Tables.

Recently I have received the requirement to store JSON strings in MySQL Database fields, to be converted into C# objects via Newtonsoft.Json, so I decided to implement a TypeConverter to convert JSON strings into custom C# Models.

Unfortunately I cannot use the following command anywhere in my solution to deserialize my JSON strings when the TypeConverter attribute is added to my C# Model:

JsonConvert.DeserializeObject<Foo>(json);

Removing the attribute resolve the issue however this prevents me from converting MySQL DB fields into custom C# objects.

Here is my C# Model with the TypeConverter Attribute added:

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(){}
}

Here is my TypeConverter Class:

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);
        }
    }
}

As soon as I add the attribute to the Foo Class I receive the following error:

Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'Models.Foo' because the type requires a JSON string value to deserialize correctly.

To fix this error either change the JSON to a JSON string value or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.

I am using the following string (which works perfectly without adding the TypeConverter Attribute):

"{\"Foo\":{\"a\":true,\"b\":false,\"c\":false}}"

Not sure what's going on here, any ideas?

Many Thanks!!!

UPDATE

I have have discovered that I also have issues with actions on MVC API Controllers that accept the Test Class with Foo as a property or on controllers that accept Foo as an object when the TypeConverter attribute is added to the Foo Class.

Here is an example of a test controller which has issues:

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;
    }
}

The TypeConverter may be causing issues overriding the WebAPI model binding and returns null when either action above receives JSON via AJAX with the following structure:

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

// eg. PostFooObj(Foo f)
{'a': false,'b': true,'c': false}

When the TypeConverter Attribute is added to the Foo Class, the following method on the FooConverter TypeConverter class is called as soon the route is found:

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

The ConvertFrom method on the FooConverter TypeController is NOT called by the ApiController's action, which may be the cause of the issue.

Again it's a similar situation, where the controllers actions will work fine without the TypeConverter Attribute.

Any further help greatly appreciated!!

Many thanks.

解决方案

There are a few things going on here. First, a preliminary issue: even with no TypeConverter applied, your JSON does not correspond to your class Foo, it corresponds to some container class that contains a Foo property, for instance:

public class TestClass
{
    public Foo Foo { get; set; }
}

I.e. given your JSON string, the following will not work:

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

But the following will:

var test = JsonConvert.DeserializeObject<TestClass>(json);

I suspect this is simply a mistake in the question, so I'll assume you are looking to deserialize a class contain a property Foo.

The main problem you are seeing is that Json.NET will try to use a TypeConverter if one is present to convert a class to be serialized to a JSON string. From the docs:

Primitive Types

.Net: TypeConverter (convertible to String)
JSON: String

But in your JSON, Foo is not a JSON string, it is a JSON object, thus deserialization fails once the type converter is applied. An embedded string would look like this:

{"Foo":"{\"a\":true,\"b\":false,\"c\":false}"}

Notice how all the quotes have been escaped. And even if you changed your JSON format for Foo objects to match this, your deserialization would still fail as the TypeConverter and Json.NET try to call each other recursively.

Thus what you need to do is to globally disable use of the TypeConverter by Json.NET and fall back to default serialization while retaining use of the TypeConverter in all other situations. This is a bit tricky since there is no Json.NET attribute you can apply to disable use of type converters, instead you need a special contract resolver plus a special JsonConverter to make use of it:

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);
    }
}

And use it like:

[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);
    }
}

Example fiddle.

Finally, you should probably also implement the ConvertTo method in your type converter, see How to: Implement a Type Converter.

这篇关于Newtonsoft.JSON不能转换模型的TypeConverter属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆