Json.net serializa / desserializa tipos derivados?

98

json.net (newtonsoft)
Estou olhando a documentação, mas não consigo encontrar nada sobre isso ou a melhor maneira de fazer isso.

public class Base
{
    public string Name;
}
public class Derived : Base
{
    public string Something;
}

JsonConvert.Deserialize<List<Base>>(text);

Agora tenho objetos derivados na lista serializada. Como faço para desserializar a lista e recuperar os tipos derivados?

Vai
fonte
Não é assim que funciona a herança. Você pode especificar JsonConvert.Deserialize <Derived> (texto); para incluir o campo Nome. Visto que Derived IS A Base (não o contrário), Base não sabe nada sobre a definição de Derived.
M.Babcock
Desculpe, esclareci um pouco. O problema é que tenho uma lista que contém objetos básicos e derivados. Portanto, preciso descobrir como dizer à newtonsoft como desserializar os itens derivados.
Será em
Eu fiz você resolver isso. Tenho o mesmo problema
Luis Carlos Chavarría

Respostas:

46

Se você estiver armazenando o tipo em seu text(como deveria neste cenário), você pode usar o JsonSerializerSettings.

Veja: como desserializar JSON em IEnumerable <BaseType> com Newtonsoft JSON.NET

Mas tenha cuidado. Usar qualquer coisa diferente TypeNameHandling = TypeNameHandling.Nonepode abrir-se a uma vulnerabilidade de segurança .

Kamranicus
fonte
24
Você também pode usar TypeNameHandling = TypeNameHandling.Auto- isso adicionará uma $typepropriedade SOMENTE para instâncias onde o tipo declarado (ou seja, Base) não corresponde ao tipo de instância (ou seja Derived). Dessa forma, ele não incha seu JSON tanto quanto TypeNameHandling.All.
AJ Richardson
Continuo recebendo o tipo de erro de resolução especificado em JSON '..., ...'. Caminho '$ type', linha 1, posição 82. Alguma ideia?
briba
3
Tenha cuidado ao usar isso em um endpoint público, pois isso abre problemas de segurança: alphabot.com/security/blog/2017/net/…
gjvdkamp
1
@gjvdkamp JEEZ obrigado por isso, eu não sabia disso. Adicionará ao meu post.
Kamranicus
96

Você deve habilitar o Tratamento de Nome de Tipo e passar isso para o (des) serializador como um parâmetro de configuração.

Base object1 = new Base() { Name = "Object1" };
Derived object2 = new Derived() { Something = "Some other thing" };
List<Base> inheritanceList = new List<Base>() { object1, object2 };

JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
string Serialized = JsonConvert.SerializeObject(inheritanceList, settings);
List<Base> deserializedList = JsonConvert.DeserializeObject<List<Base>>(Serialized, settings);

Isso resultará na desserialização correta das classes derivadas. Uma desvantagem é que nomeará todos os objetos que você está usando, assim como nomeará a lista na qual você está colocando os objetos.

Madmenyo
fonte
31
+1. Eu estava pesquisando por 30 minutos até que descobri que você precisa usar as mesmas configurações para SerializeObject e DeserializeObject. Presumi que usaria $ type implicitamente se estiver lá durante a desserialização, tolice.
Erti-Chris Eelmaa
24
TypeNameHandling.Autofará isso também, e é melhor porque não grava o nome do tipo de instância quando ele corresponde ao tipo do campo / propriedade, o que geralmente é o caso para a maioria dos campos / propriedades.
Roman Starkov de
2
Isso não funciona quando a desserialização é executada em outra solução / projeto. Na serialização, o nome da Solução é embutido como tipo: "SOLUTIONNAME.Models.Model". Na desserialização na outra solução, ele lançará "JsonSerializationException: Não foi possível carregar o assembly 'SOLUTIONNAME'.
Triste CRUD Developer
19

Visto que a pergunta é tão popular, pode ser útil acrescentar o que fazer se quiser controlar o nome da propriedade do tipo e seu valor.

O longo caminho é escrever de forma personalizada JsonConverter s para lidar com a (des) serialização verificando e configurando manualmente a propriedade type.

Uma maneira mais simples é usar JsonSubTypes , que lida com todo o boilerplate por meio de atributos:

[JsonConverter(typeof(JsonSubtypes), "Sound")]
[JsonSubtypes.KnownSubType(typeof(Dog), "Bark")]
[JsonSubtypes.KnownSubType(typeof(Cat), "Meow")]
public class Animal
{
    public virtual string Sound { get; }
    public string Color { get; set; }
}

public class Dog : Animal
{
    public override string Sound { get; } = "Bark";
    public string Breed { get; set; }
}

public class Cat : Animal
{
    public override string Sound { get; } = "Meow";
    public bool Declawed { get; set; }
}
rzippo
fonte
3
Eu entendo a necessidade, mas não sou fã de ter que tornar a classe base ciente de todos os "KnownSubType" s ...
Matt Knowles
2
Existem outras opções se você olhar a documentação. Eu apenas dei o exemplo que mais gosto.
rzippo
1
Essa é a abordagem mais segura que não expõe seu serviço a carregar tipos arbitrários na desserialização.
David Burg
3

Use este JsonKnownTypes , é uma forma muito semelhante de usar, basta adicionar discriminador ao json:

[JsonConverter(typeof(JsonKnownTypeConverter<BaseClass>))]
[JsonKnownType(typeof(Base), "base")]
[JsonKnownType(typeof(Derived), "derived")]
public class Base
{
    public string Name;
}
public class Derived : Base
{
    public string Something;
}

Agora, quando você serializar objeto em JSON será adicionar "$type"com "base"e "derived"valor e que vai ser usado para deserialize

Exemplo de lista serializada:

[
    {"Name":"some name", "$type":"base"},
    {"Name":"some name", "Something":"something", "$type":"derived"}
]
Dmitry
fonte