Newtonsoft.JSON не может преобразовать модель с атрибутом TypeConverter

у меня есть C# MVC приложение, которое хранит данные как строки JSON в XML-документе, а также в таблицах БД MySQL.

недавно я получил требование хранить строки JSON в полях базы данных MySQL, для преобразования в объекты C# через Newtonsoft.В JSON, поэтому я решил реализовать TypeConverter для преобразования строк JSON в пользовательские модели C#.

к сожалению, я не могу использовать следующую команду в любом месте в моем решении десериализовать мои строки JSON, когда атрибут TypeConverter добавлен в мою модель C#:

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"}) в модели типа".Foo', потому что для правильной десериализации типа требуется строковое значение JSON.

чтобы исправить эту ошибку, измените JSON на строковое значение JSON или измените десериализованный тип, чтобы он был обычным типом .NET (например, не примитивным типом, таким как integer, а не типом коллекции, таким как массив или список), который может быть десериализован из объекта JSON. JsonObjectAttribute также может быть добавлен к типу, чтобы заставить его десериализоваться из объекта JSON.

я использую следующую строку (которая отлично работает без добавления атрибута TypeConverter):

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

Не уверен, что здесь происходит, есть идеи?

Большое Спасибо!!!

обновление

я обнаружил, что у меня есть проблемы с действия на контроллеры API MVC что признать тестовый класс с 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 и возвращает null, когда любое действие выше получает JSON через AJAX со следующей структурой:

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

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

когда атрибут TypeConverter добавляется к классу Foo, следующий метод в классе Typeconverter FooConverter вызывается, как только маршрут найден:

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

метод ConvertFrom на FooConverter TypeController не вызывается действием ApiController, которое может быть причиной проблемы.

опять же, это аналогичная ситуация, когда действия контроллеров будут работать нормально без Атрибут TypeConverter.

помощь с благодарностью!!

большое спасибо.

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 если он присутствует для преобразования класса, который будет сериализован в строку json. От docs:

Примитивные Типы

.Объем: TypeConverter (конвертируется в строку)
JSON: строка

но в вашем JSON,Foo не Строка JSON, это JSON объект, таким образом, десериализация завершается с ошибкой после применения преобразователя типов. Встроенная строка будет выглядеть так:

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

обратите внимание, как все кавычки экранированы. И даже если вы изменили формат JSON на Foo объекты, соответствующие этому, ваша десериализация все равно завершится неудачей как TypeConverter и Json.NET попробуйте позвонить друг другу рекурсивно.

таким образом, что вам нужно сделать, это отключить глобально использование TypeConverter by Json.NET и вернуться к сериализации по умолчанию при сохранении использования 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 метод в преобразователе типов, см. как реализовать конвертер типа.


простой способ избежать этого поведения-удалить или из проверки преобразования т. е. удалить | / 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);
        }
    }
}

Если у вас есть структура, а не класс, то принятый ответ все равно перейдет в бесконечную рекурсию при попытке (de)сериализовать 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);
        }