Newtonsoft.JSON 无法转换具有 TypeConverter 属性的模型 [英] Newtonsoft.JSON cannot convert model with TypeConverter attribute

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

问题描述

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

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

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

JsonConvert.DeserializeObject(json);

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

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

使用 System.ComponentModel;[TypeConverter(typeof(FooConverter))]公开课 Foo{公共布尔一个{得到;放;}公共布尔 b { 得到;放;}公共布尔 c { 得到;放;}公共 Foo(){}}

这是我的TypeConverter 类:

使用Newtonsoft.Json;使用系统;使用 System.ComponentModel;公共类 FooConverter : TypeConverter{public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType){if (sourceType == typeof(string)){返回真;}返回 base.CanConvertFrom(context, sourceType);}公共覆盖对象 ConvertFrom(ITypeDescriptorContext 上下文,System.Globalization.CultureInfo 文化,对象值){如果(值为字符串){string s = value.ToString().Replace("\","");Foo f = JsonConvert.DeserializeObject(s);返回 f;}返回 base.ConvertFrom(上下文,文化,价值);}}}

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

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

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

我正在使用以下字符串(无需添加 TypeConverter 属性即可完美运行):

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

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

非常感谢!!!

更新

我发现我在 MVC API 控制器 上的操作也有问题,这些控制器接受 以 Foo 作为属性的测试类 或接受 的控制器将 TypeConverter 属性添加到 Foo 类时,将 Foo 作为对象.

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

公共类 TestController : ApiController{[AcceptVerbs("POST", "GET")]公共无效 PostTestClass(TestClass t){//当 TypeConverter 属性添加到 Foo 类时返回 null返回 t.Foo;}AcceptVerbs("POST", "GET")]public void PostFooObj(Foo f){//当 TypeConverter 属性添加到 Foo 类时返回 null返回 f;}}

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

//例如.PostTestClass(TestClass T){'Foo': {'a': false,'b': true,'c': false}};//例如.PostFooObj(Foo f){'a':假,'b':真,'c':假}

当 TypeConverter 属性被添加到 Foo 类时,FooConverter TypeConverter 类上的以下方法会在找到路由后立即调用:

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

FooConverter TypeController 上的 ConvertFrom 方法未被 ApiController 的操作调用,这可能是问题的原因.

再次出现类似的情况,控制器动作在没有 TypeConverter 属性的情况下也能正常工作.

非常感谢任何进一步的帮助!!

非常感谢.

解决方案

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

公共类TestClass{公共 Foo Foo { 得到;放;}}

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

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

但以下将:

var test = JsonConvert.DeserializeObject(json);

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

您看到的主要问题是 Json.NET 将尝试使用 TypeConverter(如果存在)将要序列化的类转换为 JSON 字符串.来自文档:

<块引用>

原始类型

.Net:TypeConverter(可转换为字符串)
JSON:字符串

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

<块引用>

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

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

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

公共类 NoTypeConverterJsonConverter: JsonConverter{静态只读 IContractResolver 解析器 = new NoTypeConverterContractResolver();类 NoTypeConverterContractResolver : DefaultContractResolver{受保护的覆盖 JsonContract CreateContract(Type objectType){if (typeof(T).IsAssignableFrom(objectType)){var contract = this.CreateObjectContract(objectType);contract.Converter = null;//也将转换器归零以防止无限递归.退货合同;}返回 base.CreateContract(objectType);}}public override bool CanConvert(Type objectType){返回 typeof(T).IsAssignableFrom(objectType);}公共覆盖对象 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);}}

然后像这样使用它:

[TypeConverter(typeof(FooConverter))][JsonConverter(typeof(NoTypeConverterJsonConverter))]公开课 Foo{公共布尔一个{得到;放;}公共布尔 b { 得到;放;}公共布尔 c { 得到;放;}公共 Foo() { }}公共类 FooConverter : TypeConverter{public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType){if (sourceType == typeof(string)){返回真;}返回 base.CanConvertFrom(context, sourceType);}公共覆盖对象 ConvertFrom(ITypeDescriptorContext 上下文,System.Globalization.CultureInfo 文化,对象值){如果(值为字符串){字符串 s = value.ToString();//s = s.Replace("\", "");Foo f = JsonConvert.DeserializeObject(s);返回 f;}返回 base.ConvertFrom(上下文,文化,价值);}}

示例 fiddle.

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

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天全站免登陆