JSON.NET Десериализовать интерфейс для имени атрибута

У меня есть строка JSON, например.

{
  "first_name": "Paul"
  "company": {
    "company_name": "Microsoft"
  }
}

и мой класс находится в следующей структуре

[DataContract]
class User {
  [DataMember(Name = "first_name")]
  public string FirstName { get; set; }

  [DataMember(Name = "company")]
  public ICompany Company { get; set; }
}

interface ICompany {
  string CompanyName { get; set; }
}

[DataContract]
class Company : ICompany {
  [DataMember(Name = "company_name")]
  public string CompanyName { get; set; }
}

Я использую собственный автоматический преобразователь, который просто находит реализацию интерфейса с помощью autofac (взято с веб-сайта json.net)

public class AutofacContractResolver : DefaultContractResolver
    {
        private readonly IContainer _container;

        public AutofacContractResolver(IContainer container)
        {
            _container = container;
        }

        protected override JsonObjectContract CreateObjectContract(Type objectType)
        {
            JsonObjectContract contract = base.CreateObjectContract(objectType);

            // use Autofac to create types that have been registered with it
            if (_container.IsRegistered(objectType))
            {
                contract.DefaultCreator = () => _container.Resolve(objectType);
            }

            return contract;
        }
    }

Затем я десериализую строку json, используя

User user = JsonConvert.DeserializeObject<User>(json, new JsonSerializerSettings
                {
                    ContractResolver = _resolver,

                });

Теперь, когда я десериализую json, first_name сериализуется в свойство FirstName, а company_name — нет, оно просто возвращает null. Когда я использую TraceWriter, он заявляет, что не может найти члена с именем company_name в интерфейсе ICompany (что, очевидно, не может, поскольку он установлен в классе объектов Company)

Почему JSON.NET не использует имя DataMember из объекта Company для десериализации? Поскольку он знает об объекте Compmany из пользовательского преобразователя и создает этот объект, но без значений внутри.

Как я могу заставить его использовать реализацию интерфейса, а не сам интерфейс?

К вашему сведению, это не весь объект, и я не могу просто изменить свое свойство на класс вместо интерфейса. Это просто уменьшенный пример того, что происходит со всеми объектами интерфейса.


person Carl Thomas    schedule 08.08.2016    source источник


Ответы (1)


У меня была такая же проблема, и в поисках ответа я нашел здесь ваш старый вопрос. На самом деле то, как вы спросили, навело меня на правильный путь, и я решил проблему. Для меня работает следующее:

Я не использую Autofac, так что это псевдокод. Но этого должно быть достаточно, чтобы понять это:

public class AutofacContractResolver : DefaultContractResolver
{
    private readonly IContainer _container;

    public AutofacContractResolver(IContainer container)
    {
        _container = container;
    }

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        JsonObjectContract contract;

        // use Autofac to create types that have been registered with it
        if (_container.IsRegistered(objectType))
        {
            //get the class that is mapped to the interface from autofac
            Type mappedType = _container.GetMappedTypeForRegisteredType(objectType);

            //so for example when objectType now contains 'ICompany'
            //then mappedType now should contain 'Company'

            //now use the mappedType to create the contract
            contract = base.CreateObjectContract(mappedType);

            //but still use the objectType for resolving
            contract.DefaultCreator = () => _container.Resolve(objectType);
        }
        else
            contract = base.CreateObjectContract(objectType);

        return contract;
    }
}

Контракт должен создаваться с использованием конкретной реализации, зарегистрированной в контейнере зависимостей, а не интерфейса. Разрешение типа из контейнера для DefaultCreator, очевидно, должно по-прежнему использовать интерфейс.

person alex    schedule 03.11.2017