1. Introdução
Imagine: você escreveu a classe Person, serializou um objeto em arquivo, e alguns meses depois decidiu que precisa, digamos, de um novo endereço de residência ou mudou o tipo de algumas propriedades. Parece rotina — mas quando você tenta carregar (desserializar) dados antigos, pode ter surpresas: algo não vai carregar, uma exceção é lançada, alguns valores ficam vazios ou até errados.
Esse comportamento é um caso típico de quebra de compatibilidade reversa. No desenvolvimento real isso acontece com mais frequência do que estudante esquece ponto e vírgula (ou seja, muito frequentemente).
Vamos ilustrar o problema com um exemplo
Considere nosso mini-projeto didático. Suponha que no momento tínhamos a seguinte classe:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Serializamos uma instância dessa classe em JSON:
Person p = new Person { Name = "Alice", Age = 35 };
string json = JsonSerializer.Serialize(p);
File.WriteAllText("person.json", json);
No arquivo obtemos:
{"Name":"Alice","Age":35}
Agora, uma semana depois, decidimos deixar o app mais moderno e adicionamos um campo de endereço:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; } // novo campo
}
E então tentamos carregar o arquivo antigo:
string json = File.ReadAllText("person.json");
Person p = JsonSerializer.Deserialize<Person>(json);
O que vai acontecer? O endereço no nosso objeto não vai existir: a propriedade Address ficará igual a null. Nenhum erro ocorreu. Por enquanto tudo funciona... Mas se você começar a mudar tipos, remover campos ou fazer algo realmente "interessante", os problemas podem aparecer!
2. Que tipos de mudanças existem — e como elas afetam
Mudanças na estrutura das classes afetam a serialização de formas diferentes. Vamos analisar alguns cenários típicos.
Adicionar novas propriedades
Essa é a opção menos perigosa. Dados antigos (onde essas propriedades não existiam) são desserializados tranquilamente: as novas propriedades recebem valores padrão (null para referências, 0 para int etc.).
Atenção: Se sua nova propriedade não é nullable e não tem um valor padrão "sensato", pode haver problema (especialmente com propriedades required do C# 11+).
Remover propriedades
Se você remove uma propriedade, e nos dados serializados ela ainda existe — o serializador provavelmente vai simplesmente ignorar o "excesso", e o carregamento ainda irá acontecer.
Mas isso depende do serializador usado. Por exemplo, JsonSerializer e Newtonsoft.Json são bem permissivos: eles não vão lançar exceção; já alguns serializadores antigos ou customizados podem se comportar de forma diferente.
Renomear propriedades
Aí começa a diversão. Se você só renomear a propriedade FirstName para Name, o serializador não conseguirá mapear os campos dos dados antigos para o objeto novo. Ou seja, o campo ficará vazio (null/0) e o valor antigo no arquivo será ignorado.
Mudar o tipo da propriedade
Por exemplo, antes você tinha public int Age, depois decidiu torná-lo public string Age (vai que alguém coloca "bessmertnyy" — acontece). Tentar desserializar dados antigos pode gerar erro ("Cannot convert number to string") ou a propriedade pode receber valor padrão. Tudo depende do serializador e das suas configurações de tipagem rígida.
Mudar hierarquias (herança, aninhamento)
Se você altera classes base, move propriedades para outros lugares ou, digamos, faz uma classe-encapsuladora para outra — dados serializados antigos podem ficar totalmente incompatíveis. Isso é especialmente problemático com XML e hierarquias complexas de objetos.
3. Problemas de compatibilidade
Como descobrir problemas de compatibilidade?
Muitas vezes o erro de compatibilidade não aparece imediatamente e de forma óbvia: o app só começa a se comportar "estranhamente", parte dos dados some, ou aparece uma exceção pouco informativa nos logs. Normalmente os problemas surgem quando:
- Um usuário carrega um arquivo antigo na versão nova do programa.
- O servidor recebe JSON/XML de um cliente "versão antiga".
- Você integra com uma API externa que mudou de repente.
Os sintomas variam: desde erros na desserialização até campos "inesperadamente" vazios.
Impacto do serializador na compatibilidade
Nem todos os serializadores se comportam igual. Os JSON-serializers são os mais "tolerantes" a mudanças na estrutura — tanto o padrão System.Text.Json quanto o Newtonsoft.Json. Eles costumam pular propriedades desconhecidas do arquivo e não serializam de volta campos desconhecidos do objeto.
Em XML a coisa é um pouco mais rígida: se o elemento raiz ou a hierarquia mudam, podem aparecer erros.
Em formatos binários pode rolar exceção se a ordem ou tipos mudaram!
4. Como minimizar riscos? Abordagens e práticas
Aqui vão algumas abordagens que ajudam a reduzir problemas (e às vezes evitá-los por completo).
Use versões de classes e dados
Adicione um campo especial Version nos objetos serializáveis ou nos próprios arquivos. Isso permite saber com qual versão da estrutura o arquivo foi criado e decidir o que fazer ao carregar (por exemplo, aplicar upgrade nos dados).
public class PersonV2
{
public int Version { get; set; } = 2;
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
}
Use atributos de mapeamento de nomes (para serialização)
Com JSON e XML você pode explicitar como a propriedade deve ser nomeada na forma serializada. Se renomear a propriedade — mantenha o nome antigo:
public class Person
{
[JsonPropertyName("FirstName")] // para System.Text.Json
[JsonProperty("FirstName")] // para Newtonsoft.Json
public string Name { get; set; }
public int Age { get; set; }
}
Use tipos nullable e valores padrão
Se aparecerem novos campos que nem sempre existem em dados antigos — faça-os nullable ou dê um valor padrão, assim a desserialização não quebra:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string? Address { get; set; } = "Unknown";
}
Tratamento de evento "campo desconhecido"
No Newtonsoft.Json você pode se inscrever para tratar campos "estranhos" via settings, para por exemplo logar uma situação potencialmente perigosa.
var settings = new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Error
};
try
{
var person = JsonConvert.DeserializeObject<Person>(json, settings);
}
catch (JsonSerializationException ex)
{
Console.WriteLine("Não foi possível desserializar: " + ex.Message);
}
Migração de dados
Se as mudanças são significativas, é mais sensato prever uma etapa de migração: por exemplo, carregar os dados na "estrutura antiga" e então convertê-los para a nova:
// Suponha que PersonV1 era sem address
public class PersonV1 { public string Name; public int Age; }
// Novo — com address
public class PersonV2 { public string Name; public int Age; public string Address; }
// Migração:
string oldJson = File.ReadAllText("person.json");
PersonV1 oldPerson = JsonSerializer.Deserialize<PersonV1>(oldJson);
PersonV2 migrated = new PersonV2
{
Name = oldPerson.Name,
Age = oldPerson.Age,
Address = "Unknown"
};
5. Casos complexos e erros inesperados
Invariância do campo e propriedades required
No C# 11+ surgiram propriedades required. Agora, se um campo é marcado como required, a desserialização pode lançar erro se esse campo estiver ausente nos dados:
public class Person
{
public string Name { get; set; }
[JsonPropertyName("Age")]
public required int Age { get; set; }
public string Address { get; set; }
}
Se nos dados antigos o campo Age estiver ausente — vai ocorrer uma exceção de incompatibilidade de estrutura.
Mudança de tipo: int → string
// Era:
public class Record { public int Count; }
// Virou:
public class Record { public string Count; }
Se nos dados estiver "Count":42, a desserialização para string pode funcionar (conversão esperta), mas no sentido inverso — pode lançar exceção.
Remoção da classe base
Se o objeto serializado tinha herança, e você mudou a hierarquia — desserializar arquivos antigos pode gerar erro, às vezes "silencioso", às vezes explícito.
6. Erros típicos ao trabalhar com compatibilidade
Erro #1: mudar propriedades sem pensar.
Renomear ou trocar o tipo das propriedades sem levar em conta os dados já serializados leva à perda de informação na desserialização.
Erro #2: esquecer nullable para novos campos.
Novas propriedades devem ser nullable ou ter valores padrão sensatos.
Erro #3: não testar compatibilidade reversa.
Mudou a classe — teste que arquivos/ dados antigos ainda carregam corretamente.
Erro #4: misturar atributos de bibliotecas diferentes.
Não use JsonPropertyName e JsonProperty simultaneamente na mesma propriedade.
GO TO FULL VERSION