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 из пользовательского распознавателя, и он создает этот объект, но без значений внутри.
Как я могу заставить его использовать реализацию интерфейса, а не сам интерфейс?
К вашему сведению, это не весь объект, и я не могу просто изменить свое свойство на класс вместо интерфейса. Это всего лишь уменьшенный пример того, что происходит со всеми объектами интерфейса.
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, очевидно, все равно должно использовать интерфейс.