Newtonsoft.JSON 无法转换具有 TypeConverter 属性的模型
Newtonsoft.JSON cannot convert model with TypeConverter attribute
我有一个 C# MVC 应用程序,它将数据作为 JSON 字符串存储在 XML 文档以及 MySQL 数据库表中。
最近我收到了在 MySQL 数据库字段 中 存储 JSON 字符串的要求,以便通过以下方式转换为 C# 对象Newtonsoft.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 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);
}
}
}
只要我将属性添加到 Foo Class,我就会收到以下错误:
无法将当前 JSON 对象(例如 {"name":"value"})反序列化为类型 'Models.Foo',因为该类型需要 JSON 要正确反序列化的字符串值。
要修复此错误,请将 JSON 更改为 JSON 字符串值或更改反序列化类型,使其成为普通的 .NET 类型(例如,不是原始类型类型像整数,而不是像数组或列表这样的集合类型),可以从 JSON 对象反序列化。 JsonObjectAttribute 也可以添加到类型以强制它从 JSON 对象反序列化。
我正在使用以下字符串(无需添加 TypeConverter 属性即可完美运行):
"{\"Foo\":{\"a\":true,\"b\":false,\"c\":false}}"
不确定这里发生了什么,有什么想法吗?
非常感谢!!!
更新
我发现我在 MVC API Controllers 上的操作也有问题,它接受 Test Class with Foo作为 属性 或接受 Foo 作为对象的控制器 当 TypeConverter 属性 添加到 Foo Class.
这是一个有问题的测试控制器示例:
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 模型绑定和 returns null 的问题,当上述任一操作通过 AJAX 接收 JSON 时具有以下结构:
// eg. PostTestClass(TestClass T)
{'Foo': {'a': false,'b': true,'c': false}};
// eg. PostFooObj(Foo f)
{'a': false,'b': true,'c': false}
当 TypeConverter 属性添加到 Foo Class 时,FooConverter TypeConverter class 上的以下方法会在找到路由后立即调用:
public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
ApiController 的操作未调用 FooConverter TypeController 上的 ConvertFrom 方法,这可能是问题的原因。
再次出现类似情况,控制器操作在没有 TypeConverter 属性的情况下也能正常工作。
非常感谢任何进一步的帮助!!
非常感谢。
这里发生了一些事情。首先,一个初步问题:即使没有应用 TypeConverter
,您的 JSON 也不对应于您的 class Foo
,它对应于某个容器 class,其中包含一个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);
我怀疑这只是问题中的一个错误,所以我假设您要反序列化一个 class 包含一个 属性 Foo
.
您看到的主要问题是 Json.NET 将尝试使用 TypeConverter
(如果存在)将要序列化的 class 转换为JSON 字符串。来自 docs:
Primitive Types
.Net: TypeConverter
(convertible to String)
JSON: String
但是在你的JSON中,Foo
不是一个JSON字符串,它是一个JSON对象,因此一旦应用了类型转换器,反序列化就会失败。嵌入的字符串如下所示:
{"Foo":"{\"a\":true,\"b\":false,\"c\":false}"}
注意所有引号是如何转义的。即使您更改了 Foo
对象的 JSON 格式以匹配此格式,您的反序列化仍然会失败,因为 TypeConverter
和 Json.NET 会尝试递归地相互调用。
因此,您需要做的是全局禁用 Json.NET 对 TypeConverter
的使用,并回退到默认序列化,同时在所有其他情况下保留对 TypeConverter
的使用。这有点棘手,因为没有 Json.NET attribute 你可以申请禁用类型转换器,相反你需要一个特殊的合同解析器加上一个特殊的 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);
}
}
例子fiddle.
最后,您可能还应该在类型转换器中实现 ConvertTo
方法,请参阅 How to: Implement a Type Converter。
避免这种行为的简单方法是从转换检查中删除 OR
即删除 || destinationType == typeof(字符串)
下面的例子..
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);
}
}
}
如果您的结构不是 class,那么在尝试(反)序列化 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);
}
我有一个 C# MVC 应用程序,它将数据作为 JSON 字符串存储在 XML 文档以及 MySQL 数据库表中。
最近我收到了在 MySQL 数据库字段 中 存储 JSON 字符串的要求,以便通过以下方式转换为 C# 对象Newtonsoft.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 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);
}
}
}
只要我将属性添加到 Foo Class,我就会收到以下错误:
无法将当前 JSON 对象(例如 {"name":"value"})反序列化为类型 'Models.Foo',因为该类型需要 JSON 要正确反序列化的字符串值。
要修复此错误,请将 JSON 更改为 JSON 字符串值或更改反序列化类型,使其成为普通的 .NET 类型(例如,不是原始类型类型像整数,而不是像数组或列表这样的集合类型),可以从 JSON 对象反序列化。 JsonObjectAttribute 也可以添加到类型以强制它从 JSON 对象反序列化。
我正在使用以下字符串(无需添加 TypeConverter 属性即可完美运行):
"{\"Foo\":{\"a\":true,\"b\":false,\"c\":false}}"
不确定这里发生了什么,有什么想法吗?
非常感谢!!!
更新
我发现我在 MVC API Controllers 上的操作也有问题,它接受 Test Class with Foo作为 属性 或接受 Foo 作为对象的控制器 当 TypeConverter 属性 添加到 Foo Class.
这是一个有问题的测试控制器示例:
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 模型绑定和 returns null 的问题,当上述任一操作通过 AJAX 接收 JSON 时具有以下结构:
// eg. PostTestClass(TestClass T)
{'Foo': {'a': false,'b': true,'c': false}};
// eg. PostFooObj(Foo f)
{'a': false,'b': true,'c': false}
当 TypeConverter 属性添加到 Foo Class 时,FooConverter TypeConverter class 上的以下方法会在找到路由后立即调用:
public override bool CanConvertFrom(ITypeDescriptorContext context, System.Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
ApiController 的操作未调用 FooConverter TypeController 上的 ConvertFrom 方法,这可能是问题的原因。
再次出现类似情况,控制器操作在没有 TypeConverter 属性的情况下也能正常工作。
非常感谢任何进一步的帮助!!
非常感谢。
这里发生了一些事情。首先,一个初步问题:即使没有应用 TypeConverter
,您的 JSON 也不对应于您的 class Foo
,它对应于某个容器 class,其中包含一个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);
我怀疑这只是问题中的一个错误,所以我假设您要反序列化一个 class 包含一个 属性 Foo
.
您看到的主要问题是 Json.NET 将尝试使用 TypeConverter
(如果存在)将要序列化的 class 转换为JSON 字符串。来自 docs:
Primitive Types
.Net:
TypeConverter
(convertible to String)
JSON: String
但是在你的JSON中,Foo
不是一个JSON字符串,它是一个JSON对象,因此一旦应用了类型转换器,反序列化就会失败。嵌入的字符串如下所示:
{"Foo":"{\"a\":true,\"b\":false,\"c\":false}"}
注意所有引号是如何转义的。即使您更改了 Foo
对象的 JSON 格式以匹配此格式,您的反序列化仍然会失败,因为 TypeConverter
和 Json.NET 会尝试递归地相互调用。
因此,您需要做的是全局禁用 Json.NET 对 TypeConverter
的使用,并回退到默认序列化,同时在所有其他情况下保留对 TypeConverter
的使用。这有点棘手,因为没有 Json.NET attribute 你可以申请禁用类型转换器,相反你需要一个特殊的合同解析器加上一个特殊的 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);
}
}
例子fiddle.
最后,您可能还应该在类型转换器中实现 ConvertTo
方法,请参阅 How to: Implement a Type Converter。
避免这种行为的简单方法是从转换检查中删除 OR 即删除 || destinationType == typeof(字符串)
下面的例子..
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);
}
}
}
如果您的结构不是 class,那么在尝试(反)序列化 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);
}