Eu tenho uma definição de classe que contém uma propriedade que retorna uma interface.
public class Foo
{
public int Number { get; set; }
public ISomething Thing { get; set; }
}
A tentativa de serializar a classe Foo usando Json.NET me dá uma mensagem de erro como, "Não foi possível criar uma instância do tipo 'ISomething'. ISalgo pode ser uma interface ou classe abstrata."
Existe um atributo ou conversor Json.NET que me permita especificar uma Something
classe concreta para usar durante a desserialização?
.net
serialization
json.net
dthrasher
fonte
fonte
Respostas:
Uma das coisas que você pode fazer com Json.NET é:
var settings = new JsonSerializerSettings(); settings.TypeNameHandling = TypeNameHandling.Objects; JsonConvert.SerializeObject(entity, Formatting.Indented, settings);
O
TypeNameHandling
sinalizador adicionará uma$type
propriedade ao JSON, que permite ao Json.NET saber em qual tipo concreto ele precisa para desserializar o objeto. Isso permite que você desserialize um objeto enquanto preenche uma interface ou classe base abstrata.A desvantagem, entretanto, é que isso é muito específico do Json.NET. O
$type
será um tipo totalmente qualificado, por isso, se você está serialização-lo com a informação tipo ,, as necessidades Deserializer para ser capaz de compreendê-lo bem.Documentação: Configurações de serialização com Json.NET
fonte
TypeNameHandling
. Consulte TypeNameHandling cautela em Newtonsoft Json para obter detalhes.Você pode conseguir isso usando a classe JsonConverter. Suponha que você tenha uma classe com uma propriedade de interface;
public class Organisation { public string Name { get; set; } [JsonConverter(typeof(TycoonConverter))] public IPerson Owner { get; set; } } public interface IPerson { string Name { get; set; } } public class Tycoon : IPerson { public string Name { get; set; } }
Seu JsonConverter é responsável por serializar e desserializar a propriedade subjacente;
public class TycoonConverter : JsonConverter { public override bool CanConvert(Type objectType) { return (objectType == typeof(IPerson)); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { return serializer.Deserialize<Tycoon>(reader); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { // Left as an exercise to the reader :) throw new NotImplementedException(); } }
Quando você trabalha com uma organização desserializada via Json.Net, o IPerson subjacente da propriedade Owner será do tipo Tycoon.
fonte
Em vez de passar um objeto JsonSerializerSettings personalizado para JsonConvert.SerializeObject () com a opção TypeNameHandling.Objects, conforme mencionado anteriormente, você pode apenas marcar essa propriedade de interface específica com um atributo para que o JSON gerado não seja inflado com propriedades "$ type" em CADA objeto:
public class Foo { public int Number { get; set; } // Add "$type" property containing type info of concrete class. [JsonProperty( TypeNameHandling = TypeNameHandling.Objects )] public ISomething { get; set; } }
fonte
Na versão mais recente do conversor Newtonsoft Json de terceiros, você pode definir um construtor com um tipo concreto relacionado à propriedade de interface.
public class Foo { public int Number { get; private set; } public ISomething IsSomething { get; private set; } public Foo(int number, Something concreteType) { Number = number; IsSomething = concreteType; } }
Contanto que algo implemente ISalgo, isso deve funcionar. Além disso, não coloque um construtor vazio padrão no caso de o conversor JSon tentar usá-lo, você deve forçá-lo a usar o construtor que contém o tipo concreto.
PS. isso também permite que você torne seus setters privados.
fonte
Tive o mesmo problema, então criei meu próprio conversor que usa o argumento de tipos conhecidos.
public class JsonKnownTypeConverter : JsonConverter { public IEnumerable<Type> KnownTypes { get; set; } public JsonKnownTypeConverter(IEnumerable<Type> knownTypes) { KnownTypes = knownTypes; } protected object Create(Type objectType, JObject jObject) { if (jObject["$type"] != null) { string typeName = jObject["$type"].ToString(); return Activator.CreateInstance(KnownTypes.First(x =>typeName.Contains("."+x.Name+","))); } throw new InvalidOperationException("No supported type"); } public override bool CanConvert(Type objectType) { if (KnownTypes == null) return false; return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // Load JObject from stream JObject jObject = JObject.Load(reader); // Create target object based on JObject var target = Create(objectType, jObject); // Populate the object properties serializer.Populate(jObject.CreateReader(), target); return target; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } }
Eu defini dois métodos de extensão para desserializar e serializar:
public static class AltiJsonSerializer { public static T DeserializeJson<T>(this string jsonString, IEnumerable<Type> knownTypes = null) { if (string.IsNullOrEmpty(jsonString)) return default(T); return JsonConvert.DeserializeObject<T>(jsonString, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto, Converters = new List<JsonConverter> ( new JsonConverter[] { new JsonKnownTypeConverter(knownTypes) } ) } ); } public static string SerializeJson(this object objectToSerialize) { return JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented, new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto}); } }
Você pode definir sua própria maneira de comparar e identificar tipos nos convertidos, eu apenas uso o nome da classe.
fonte
Normalmente, sempre usei a solução com,
TypeNameHandling
conforme sugerido por DanielT, mas nos casos aqui não tive controle sobre o JSON de entrada (e, portanto, não posso garantir que inclua uma$type
propriedade), escrevi um conversor personalizado que apenas permite que você especifique explicitamente o tipo de concreto:public class Model { [JsonConverter(typeof(ConcreteTypeConverter<Something>))] public ISomething TheThing { get; set; } }
Isso apenas usa a implementação do serializador padrão do Json.Net enquanto especifica explicitamente o tipo concreto.
O código-fonte e uma visão geral estão disponíveis nesta postagem do blog .
fonte
Eu só queria completar o exemplo que @Daniel T. nos mostrou acima:
Se você estiver usando este código para serializar seu objeto:
var settings = new JsonSerializerSettings(); settings.TypeNameHandling = TypeNameHandling.Objects; JsonConvert.SerializeObject(entity, Formatting.Indented, settings);
O código para desserializar o json deve ser assim:
var settings = new JsonSerializerSettings(); settings.TypeNameHandling = TypeNameHandling.Objects; var entity = JsonConvert.DeserializeObject<EntityType>(json, settings);
É assim que um json é conformado ao usar o
TypeNameHandling
sinalizador:fonte
Eu me perguntei a mesma coisa, mas temo que não possa ser feito.
Vamos ver desta forma. Você entrega ao JSon.net uma string de dados e um tipo para desserializar. O que o JSON.net deve fazer quando atingir esse ISomething? Ele não pode criar um novo tipo de ISomething porque ISomething não é um objeto. Ele também não pode criar um objeto que implemente ISomething, uma vez que não tem ideia de qual dos muitos objetos que podem herdar ISomething ele deve usar. Interfaces são algo que pode ser serializado automaticamente, mas não desserializado automaticamente.
O que eu faria seria substituir ISomething por uma classe base. Usando isso, você poderá obter o efeito que está procurando.
fonte
Aqui está uma referência a um artigo escrito por ScottGu
Com base nisso, escrevi um código que acho que pode ser útil
public interface IEducationalInstitute { string Name { get; set; } } public class School : IEducationalInstitute { private string name; #region IEducationalInstitute Members public string Name { get { return name; } set { name = value; } } #endregion } public class Student { public IEducationalInstitute LocalSchool { get; set; } public int ID { get; set; } } public static class JSONHelper { public static string ToJSON(this object obj) { JavaScriptSerializer serializer = new JavaScriptSerializer(); return serializer.Serialize(obj); } public static string ToJSON(this object obj, int depth) { JavaScriptSerializer serializer = new JavaScriptSerializer(); serializer.RecursionLimit = depth; return serializer.Serialize(obj); } }
E é assim que você o chamaria
School myFavSchool = new School() { Name = "JFK High School" }; Student sam = new Student() { ID = 1, LocalSchool = myFavSchool }; string jSONstring = sam.ToJSON(); Console.WriteLine(jSONstring); //Result {"LocalSchool":{"Name":"JFK High School"},"ID":1}
Se bem entendi, não acho que você precise especificar uma classe concreta que implemente a interface para serialização JSON.
fonte