【问题标题】:Newtonsoft.JSON cannot convert model with TypeConverter attributeNewtonsoft.JSON 无法转换具有 TypeConverter 属性的模型
【发布时间】:2015-09-28 07:45:24
【问题描述】:

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

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

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

JsonConvert.DeserializeObject<Foo>(json);

删除该属性可解决问题,但这会阻止我将 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(){}
}

这是我的 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.Foo”类型,因为该类型需要 JSON 字符串值才能正确反序列化。

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

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

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

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

非常感谢!!!

更新

我发现在接受 Test Class with Foo 作为属性MVC API 控制器 或接受 的控制器上的操作也存在问题当 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;
    }
}

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

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

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

将TypeConverter Attribute添加到Foo类时,一旦找到路由,就会调用FooConverter TypeConverter类上的以下方法:

    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 属性,控制器的操作也可以正常工作。

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

非常感谢。

【问题讨论】:

  • 您的 JSON 字符串是否固定为您所显示的 "{\"Foo\":...}"
  • 是的,您是正确的,JSON 在我的测试解决方案中没有改变。我所做的只是添加/删除 TypeConverter 属性。谢谢
  • 我很好奇您是否可以使用更类似于 "{\"a\":true,\"b\":false,\"c\":false}" 的 JSON 结构,例如没有包装 "{\"Foo\":...}" 对象。这个 JSON 可以直接使用 JsonConvert.DeserializeObject&lt;Foo&gt;(json) 反序列化

标签: c# json serialization json.net typeconverter


【解决方案1】:

如果您的结构不是类,则在尝试(反)序列化 Nullable&lt;Foo&gt; 时,接受的答案仍将进入无限递归。

为避免如下修改 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);
        }

【讨论】:

    【解决方案2】:

    避免这种行为的简单方法是从转换检查中删除 OR 即删除 || 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);
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      这里发生了一些事情。首先,一个初步的问题:即使没有应用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 将尝试使用 TypeConverter 将要序列化的类转换为 JSON 字符串。来自docs

      原始类型

      .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 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

      【讨论】:

      • 太棒了,解决我的问题就像一个魅力。你是对的,在格式化 SO 问题示例的代码时,我在问题中的 JSON 确实出错了——我正在反序列化一个以 Foo 作为属性的类,有时还反序列化整个 Foo 类本身。不幸的是,我发现在接受 Test Class with Foo as a propertyMVC API Controllers 或接受 Foo 作为属性的控制器上的操作也存在问题TypeConverter 属性 添加到 Foo 类时的对象。我很快就会举一个例子,到目前为止非常感谢@dbc
      • @theMayer:我是否应该将我的更新作为一个新问题发布并将其标记为已完成?谢谢
      • 可能。我在所有曲折中遇到了麻烦。
      • 我刚刚遇到了这个问题,感谢您的参考,我通过添加 CanConvertTo 解决了这个问题,如果目标类型是 String,则返回 false。没有必要拥有 NoTypeConverterJsonConverter。当 Foo 作为属性时,WebApi Controller 也没有问题。
      • 解决方案对我来说很好,非常感谢!但我认为这是 Json.net 的设计问题。如果我有一个实际上应该转换为字符串并被json序列化的类怎么办?!?或者有问题的类是在外部(第三方)库中定义(和属性化)的,我无法更改它,但我确实想在我的消费应用程序中序列化它?
      猜你喜欢
      • 2012-01-26
      • 2021-12-12
      • 1970-01-01
      • 2017-03-22
      • 1970-01-01
      • 2021-02-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多