Приведение интерфейсов для десериализации в JSON.NET - PullRequest
111 голосов
/ 25 апреля 2011

Я пытаюсь настроить читатель, который будет принимать объекты JSON с различных веб-сайтов (например, копирование информации) и переводить их в объекты C #. В настоящее время я использую JSON.NET для процесса десериализации. Проблема, с которой я сталкиваюсь, заключается в том, что он не знает, как обрабатывать свойства уровня интерфейса в классе. Итак, что-то от природы:

public IThingy Thing

выдаст ошибку:

Не удалось создать экземпляр типа IThingy. Тип - это интерфейс или абстрактный класс, для которого нельзя создать экземпляр.

Относительно важно, чтобы это был IThingy, а не Thingy, поскольку код, над которым я работаю, считается чувствительным, а модульное тестирование очень важно. Пересмешивание объектов для сценариев атомарного тестирования невозможно с такими полноценными объектами, как Thingy. Они должны быть интерфейсом.

Я уже некоторое время изучаю документацию JSON.NET, и вопросы, которые я могу найти на этом сайте, связаны с более чем год назад. Любая помощь?

Кроме того, если это имеет значение, мое приложение написано на .NET 4.0.

Ответы [ 15 ]

2 голосов
/ 12 августа 2017

Прошло несколько лет, и у меня возникла похожая проблема. В моем случае были сильно вложенные интерфейсы и предпочтение генерировать конкретные классы во время выполнения, чтобы он работал с универсальным классом.

Я решил создать прокси-класс во время выполнения, чтобы обернуть объект, возвращаемый Newtonsoft.

Преимущество этого подхода состоит в том, что он не требует конкретной реализации класса и может автоматически обрабатывать любую глубину вложенных интерфейсов. Вы можете увидеть больше об этом в моем блоге .

using Castle.DynamicProxy;
using Newtonsoft.Json.Linq;
using System;
using System.Reflection;

namespace LL.Utilities.Std.Json
{
    public static class JObjectExtension
    {
        private static ProxyGenerator _generator = new ProxyGenerator();

        public static dynamic toProxy(this JObject targetObject, Type interfaceType) 
        {
            return _generator.CreateInterfaceProxyWithoutTarget(interfaceType, new JObjectInterceptor(targetObject));
        }

        public static InterfaceType toProxy<InterfaceType>(this JObject targetObject)
        {

            return toProxy(targetObject, typeof(InterfaceType));
        }
    }

    [Serializable]
    public class JObjectInterceptor : IInterceptor
    {
        private JObject _target;

        public JObjectInterceptor(JObject target)
        {
            _target = target;
        }
        public void Intercept(IInvocation invocation)
        {

            var methodName = invocation.Method.Name;
            if(invocation.Method.IsSpecialName && methodName.StartsWith("get_"))
            {
                var returnType = invocation.Method.ReturnType;
                methodName = methodName.Substring(4);

                if (_target == null || _target[methodName] == null)
                {
                    if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
                    {

                        invocation.ReturnValue = null;
                        return;
                    }

                }

                if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
                {
                    invocation.ReturnValue = _target[methodName].ToObject(returnType);
                }
                else
                {
                    invocation.ReturnValue = ((JObject)_target[methodName]).toProxy(returnType);
                }
            }
            else
            {
                throw new NotImplementedException("Only get accessors are implemented in proxy");
            }

        }
    }



}

Использование:

var jObj = JObject.Parse(input);
InterfaceType proxyObject = jObj.toProxy<InterfaceType>();
2 голосов
/ 19 мая 2011

Для чего бы это ни стоило, мне пришлось самому по большей части справиться с этим.Каждый объект имеет метод Deserialize (string jsonStream) .Вот несколько фрагментов:

JObject parsedJson = this.ParseJson(jsonStream);
object thingyObjectJson = (object)parsedJson["thing"];
this.Thing = new Thingy(Convert.ToString(thingyObjectJson));

В этом случае new Thingy (string) - это конструктор, который вызовет метод Deserialize (string jsonStream) соответствующий тип бетона.Эта схема будет продолжать идти вниз и вниз до тех пор, пока вы не доберетесь до базовых точек, с которыми json.NET может справиться.

this.Name = (string)parsedJson["name"];
this.CreatedTime = DateTime.Parse((string)parsedJson["created_time"]);

И так далее, и тому подобное.Эта настройка позволила мне предоставить json.NET-установки, которые он может обрабатывать, без необходимости рефакторинга значительной части самой библиотеки или использования громоздких моделей try / parse, которые могли бы затопить всю нашу библиотеку из-за количества задействованных объектов.Это также означает, что я могу эффективно обрабатывать любые изменения json для конкретного объекта, и мне не нужно беспокоиться обо всем, что касается объекта.Это ни в коем случае не идеальное решение, но оно довольно хорошо работает на нашем модульном и интеграционном тестировании.

1 голос
/ 21 января 2016

Мое решение, которое мне нравится, потому что оно довольно общее, следующее:

/// <summary>
/// Automagically convert known interfaces to (specific) concrete classes on deserialisation
/// </summary>
public class WithMocksJsonConverter : JsonConverter
{
    /// <summary>
    /// The interfaces I know how to instantiate mapped to the classes with which I shall instantiate them, as a Dictionary.
    /// </summary>
    private readonly Dictionary<Type,Type> conversions = new Dictionary<Type,Type>() { 
        { typeof(IOne), typeof(MockOne) },
        { typeof(ITwo), typeof(MockTwo) },
        { typeof(IThree), typeof(MockThree) },
        { typeof(IFour), typeof(MockFour) }
    };

    /// <summary>
    /// Can I convert an object of this type?
    /// </summary>
    /// <param name="objectType">The type under consideration</param>
    /// <returns>True if I can convert the type under consideration, else false.</returns>
    public override bool CanConvert(Type objectType)
    {
        return conversions.Keys.Contains(objectType);
    }

    /// <summary>
    /// Attempt to read an object of the specified type from this reader.
    /// </summary>
    /// <param name="reader">The reader from which I read.</param>
    /// <param name="objectType">The type of object I'm trying to read, anticipated to be one I can convert.</param>
    /// <param name="existingValue">The existing value of the object being read.</param>
    /// <param name="serializer">The serializer invoking this request.</param>
    /// <returns>An object of the type into which I convert the specified objectType.</returns>
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        try
        {
            return serializer.Deserialize(reader, this.conversions[objectType]);
        }
        catch (Exception)
        {
            throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
        }
    }

    /// <summary>
    /// Not yet implemented.
    /// </summary>
    /// <param name="writer">The writer to which I would write.</param>
    /// <param name="value">The value I am attempting to write.</param>
    /// <param name="serializer">the serializer invoking this request.</param>
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

}

Очевидно, что вы можете легко и просто преобразовать его в еще более общий преобразователь, добавив конструктор, который принимает аргумент типа Dictionary для создания экземпляра переменной экземпляра преобразования.

1 голос
/ 25 апреля 2011

Ни один объект не будет когда-либо быть IThingy, поскольку все интерфейсы являются абстрактными по определению.

Объект, который у вас был, который был первый сериализован, имел некоторый тип бетона , реализующий интерфейс abstract .Вам нужно, чтобы этот же конкретный класс восстановил сериализованные данные.

Получившийся объект будет иметь такой тип, который реализует abstract интерфейс, который вы ищете.

Из документации следует, что вы можете использовать

(Thingy)JsonConvert.DeserializeObject(jsonString, typeof(Thingy));

при десериализации, чтобы сообщить JSON.NET о конкретном типе.

0 голосов
/ 18 октября 2017

В мое решение были добавлены элементы интерфейса в конструкторе.

public class Customer: ICustomer{
     public Customer(Details details){
          Details = details;
     }

     [JsonProperty("Details",NullValueHnadling = NullValueHandling.Ignore)]
     public IDetails Details {get; set;}
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...