Newtonsoft.JSON不能转换带有TypeConverter属性的模型

本文关键字:TypeConverter 属性 模型 JSON 不能 转换 Newtonsoft | 更新日期: 2023-09-27 18:01:59

我有一个 c# MVC应用程序,它将数据存储为XML文档中的JSON字符串,也存储在MySQL DB表中。

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

不幸的是,当TypeConverter属性被添加到我的c#模型:

时,我不能在我的解决方案中的任何地方使用以下命令来反序列化我的JSON字符串:
JsonConvert.DeserializeObject<Foo>(json);

删除属性解决了这个问题,但是这阻止了我将MySQL数据库字段转换为自定义c#对象。

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

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

这是我的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);
        }
    }
}

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

无法将当前JSON对象(例如{"name":"value"})反序列化为类型'Models '。

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

我使用以下字符串(无需添加TypeConverter属性即可完美工作):

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

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

多谢! !

<标题> 更新

我已经发现,我也有MVC API控制器上的动作问题,接受测试类与Foo作为属性或控制器上接受Foo作为对象TypeConverter属性添加到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;
    }
}

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}

当TypeConverter属性被添加到Foo类时,一旦找到路由,就会调用FooConverter TypeConverter类的以下方法:

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

FooConverter TypeController的ConvertFrom方法没有被ApiController的动作调用,这可能是问题的原因。

这又是一个类似的情况,控制器的动作没有TypeConverter属性也能正常工作。

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

多谢。

Newtonsoft.JSON不能转换带有TypeConverter属性的模型

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

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

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

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

但以下将:

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

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

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

原始类型

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

但是在JSON中,Foo不是JSON字符串,它是JSON 对象,因此,一旦应用类型转换器,反序列化就会失败。嵌入的字符串看起来像这样:

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

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

因此,您需要做的是全局禁用Json使用TypeConverter。并回落到默认序列化,同时在所有其他情况下保留使用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);
    }
}

并像这样使用:

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

小提琴例子。

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

避免这种行为的简单方法是从转换检查中删除OR即remove || destinationType == typeof(string)

    public class DepartmentBindModelConverter : TypeConverter
    {
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            return destinationType == typeof(DepartmentViewModel); // Removed || destinationType == typeof(string), to allow newtonsoft json convert model with typeconverter attribute
        }
        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (value == null)
                return null;
            if (destinationType == typeof(DepartmentViewModel) && value is DepartmentBindModel)
            {
                var department = (DepartmentBindModel) value;
                return new DepartmentViewModel
                {
                    Id = department.Id,
                    Name = department.Name,
                    GroupName = department.GroupName,
                    ReturnUrl = department.ReturnUrl
                };
            }
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
}

如果您有一个结构体而不是类,那么当尝试(反)序列化Nullable<Foo>时,接受的答案仍然会进入无限递归。

避免对CreateContract进行如下修改:

        protected override JsonContract CreateContract(Type objectType)
        {
            if (typeof(T).IsAssignableFrom(objectType)
                || Nullable.GetUnderlyingType(objectType) != null && typeof(T).IsAssignableFrom(Nullable.GetUnderlyingType(objectType)))
            {
                var contract = this.CreateObjectContract(objectType);
                contract.Converter = null; // Also null out the converter to prevent infinite recursion.
                return contract;
            }
            return base.CreateContract(objectType);
        }