Gerenciar serialização e desserialização de objetos complexos
Quando você está trabalhando com objetos complexos, a classe e os JsonSerializerOptions
DTOs (Objetos de Transferência de Dados) podem ser usados para evitar problemas de processamento. Objetos complexos geralmente contêm estruturas aninhadas, tipos de dados especiais ou exigem configurações específicas para lidar com serialização e desserialização com eficiência. A JsonSerializerOptions
classe fornece várias propriedades que podem ser personalizadas para resolver esses desafios. Por exemplo, as propriedades podem ser usadas para adicionar conversores personalizados, habilitar a correspondência de propriedades que não diferenciam maiúsculas de minúsculas ou lidar com valores nulos. Os DTOs, por outro lado, servem como objetos intermediários que simplificam a transferência de dados entre diferentes camadas de um aplicativo, garantindo que a estrutura e os tipos dos dados sejam consistentes e gerenciáveis. Aproveitando JsonSerializerOptions
e DTOs, os desenvolvedores podem obter serialização e desserialização mais confiáveis e eficientes de dados JSON (JavaScript Object Notation) complexos, facilitando o trabalho e a integração em seus aplicativos.
Usar a classe JsonSerializerOptions para ajudar a serializar objetos complexos
A JsonSerializerOptions
classe faz parte do System.Text.Json
namespace e fornece uma maneira de configurar o comportamento da serialização e desserialização JSON. Ele permite personalizar vários aspectos do processo de serialização, como como as propriedades são tratadas, como as referências são gerenciadas e como os tipos de dados especiais são processados.
A JsonSerializerOptions
classe é útil ao lidar com objetos complexos, pois fornece opções para lidar com estruturas aninhadas, referências circulares e outros desafios de serialização.
As seguintes propriedades são demonstradas durante esta unidade:
- MaxDepth: Essa propriedade define a profundidade máxima permitida ao ler ou gravar JSON. Ele pode ajudar a evitar problemas com objetos profundamente aninhados que podem causar estouro de pilha ou problemas de desempenho.
- ReferenceHandler: Essa propriedade permite que você especifique como as referências a objetos são tratadas durante a serialização e a desserialização. Pode ser útil ao lidar com referências circulares ou grafos de objetos complexos.
Outra propriedade que ajuda na serialização e desserialização de objetos complexos é Converters
. Essa propriedade permite que você adicione conversores personalizados para lidar com tipos específicos ou objetos complexos. Por exemplo, você pode criar um conversor personalizado para uma classe que contém objetos aninhados ou propriedades complexas.
Usar a propriedade JsonSerializerOptions.ReferenceHandler
A ReferenceHandler
propriedade é usada para especificar como as referências a objetos são tratadas durante a serialização e desserialização. Isso é particularmente útil ao lidar com grafos de objeto complexos que podem conter referências circulares ou referências compartilhadas.
Uma referência circular ocorre quando dois ou mais objetos fazem referência um ao outro, criando um loop. Por exemplo, se você tiver um objeto A
que faça referência ao objeto B
e B
ao objeto referenciar o objeto A
, isso criará uma referência circular. Quando você serializa esses objetos, o serializador pode encontrar um loop infinito, levando a um erro de estouro de pilha.
Considere o exemplo de código a seguir que demonstra uma referência circular entre duas classes:
public class Person
{
public string Name { get; set; }
public List<Pet> Pets { get; set; }
}
public class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}
Neste exemplo, a Person
classe tem uma lista de Pet
objetos e cada Pet
objeto tem uma referência de volta ao seu proprietário (o Person
). Isso cria uma referência circular quando você está serializando ou desserializando os objetos.
Serializar o Person
objeto com as configurações padrão resulta em uma JsonException
referência circular. Para lidar com isso, você pode usar a ReferenceHandler
propriedade da JsonSerializerOptions
classe.
var options = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.Preserve,
WriteIndented = true
};
var person = new Person
{
Name = "John",
Pets = new List<Pet>
{
new Pet { Name = "Fido", Owner = null },
new Pet { Name = "Whiskers", Owner = null }
}
};
person.Pets[0].Owner = person;
person.Pets[1].Owner = person;
var json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json);
/*
OUTPUT (with WriteIndented = true):
{
"$id": "1",
"Name": "John",
"Pets": {
"$id": "2",
"$values": [
{
"$id": "3",
"Name": "Fido",
"Owner": {
"$ref": "1"
}
},
{
"$id": "4",
"Name": "Whiskers",
"Owner": {
"$ref": "1"
}
}
]
}
}
*/
Neste exemplo, a opção ReferenceHandler.Preserve
é usada para manipular referências circulares. O serializador inclui uma $id
propriedade na saída JSON para representar a referência, permitindo que ela seja desserializada corretamente.
Ao desserializar, a opção ReferenceHandler.Preserve
também garante que as referências sejam restauradas corretamente, impedindo que objetos duplicados sejam criados.
var deserializedPerson = JsonSerializer.Deserialize<Person>(json, options);
Console.WriteLine($"Name: {deserializedPerson.Name}");
foreach (var pet in deserializedPerson.Pets)
{
Console.WriteLine($"Pet Name: {pet.Name}, Owner: {pet.Owner.Name}");
}
/*
OUTPUT:
Name: John
Pet Name: Fido, Owner: John
Pet Name: Whiskers, Owner: John
*/
Neste exemplo, o objeto desserializado Person
tem as mesmas referências que o objeto original, preservando as relações entre o objeto e Pet
os Person
objetos.
Usar objetos de transferência de dados para ajudar a serializar e desserializar objetos complexos
Objetos de Transferência de Dados geralmente são usados em cenários em que você precisa serializar e desserializar objetos complexos.
Os DTOs fornecem os seguintes benefícios:
- Estrutura de dados simplificada: os DTOs podem simplificar a estrutura de dados nivelando objetos aninhados ou removendo propriedades desnecessárias. Isso facilita a serialização e desserialização dos dados sem lidar com grafos de objeto complexos.
- Serialização seletiva: os DTOs permitem controlar quais propriedades estão incluídas na saída serializada. Isso pode ajudar a reduzir o tamanho dos dados serializados e melhorar o desempenho.
- Desacoplamento: os DTOs separam a estrutura de dados da lógica de negócios, facilitando o gerenciamento e a manutenção do código. Essa separação de preocupações também facilita a alteração do formato de serialização sem afetar o restante do aplicativo.
- Contratos de dados: os DTOs atuam como contratos de dados, definindo a estrutura dos dados serializados e desserializados. Isso torna mais fácil garantir a consistência e a compatibilidade entre diferentes partes do aplicativo ou entre aplicativos diferentes.
- Interoperabilidade: os DTOs podem ajudar a garantir que a estrutura de dados seja compatível com outros sistemas ou serviços, facilitando a integração com APIs ou serviços externos.
- Controle de versão: os DTOs podem ajudar a gerenciar o controle de versão de estruturas de dados, permitindo que você evolua o modelo de dados sem interromper os clientes ou serviços existentes.
- Segurança: os DTOs podem ajudar a melhorar a segurança limitando a exposição de estruturas de dados internas. Usando DTOs, você pode controlar quais propriedades são serializadas e desserializadas, reduzindo o risco de expor informações confidenciais.
- Desempenho: os DTOs podem ajudar a melhorar o desempenho reduzindo a quantidade de dados transferidos pela rede. Usando DTOs, você pode serializar apenas as propriedades necessárias, reduzindo o tamanho dos dados serializados e melhorando o desempenho.
Você pode serializar seletivamente partes de um objeto criando um DTO personalizado que inclui apenas as propriedades que você deseja serializar. Essa abordagem permite controlar exatamente o que é serializado sem incluir objetos aninhados.
Criando e usando DTOs para serialização
Aqui está um guia passo a passo sobre como criar e usar DTOs para serialização em C#:
- Defina o DTO: crie uma classe que contenha apenas as propriedades e os resultados do método que você deseja serializar.
- Mapeie o objeto original para o DTO: crie um método para mapear o objeto original para o DTO. Esse método extrai os dados necessários do objeto original e popula o DTO.
- Serializar o DTO: use a
JsonSerializer
classe para serializar o DTO em uma cadeia de caracteres JSON. - Desserializar o DTO: use a
JsonSerializer
classe para desserializar a cadeia de caracteres JSON de volta para o DTO. - Mapeie o DTO de volta para o objeto original: crie um método para mapear o DTO de volta para o objeto original. Esse método extrai os dados do DTO e preenche o objeto original.
O exemplo a seguir demonstra como criar e usar DTOs para serialização e desserialização de objetos complexos:
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
public class Company
{
public string Name { get; set; }
public List<Employee> Employees { get; set; }
// Constructor to initialize the Employees list
public Company(Employee employee)
{
Name = "Contoso Ltd";
Employees = new List<Employee> { employee };
}
}
public class Employee
{
public string Name { get; set; }
public int Age { get; set; }
public string Gender { get; set; }
public string Address { get; set; }
public string Email { get; set; }
public int EmployeeId { get; set; }
public double Salary { get; set; }
public List<Person> Dependents { get; set; }
public List<Person> EmergencyContacts { get; set; }
// Constructor to initialize the lists
public Employee()
{
Dependents = new List<Person>();
EmergencyContacts = new List<Person>();
}
}
public class EmployeeDTO
{
public string Name { get; set; }
public int EmployeeId { get; set; }
}
class Program
{
static void Main()
{
// Create an Employee object
Employee employee = new Employee
{
Name = "Elize Harmsen",
Age = 35,
Gender = "Female",
Address = "123 Main St",
Email = "elize@example.com",
EmployeeId = 101,
Salary = 75000,
Dependents = new List<Person>
{
new Person { Name = "Peter Zammit", Age = 35 }
},
EmergencyContacts = new List<Person>
{
new Person { Name = "Anette Thomsen", Age = 40 }
}
};
// Create a Company object with the Employee
Company company = new Company(employee);
// Map Employee to EmployeeDTO
EmployeeDTO employeeDTO = new EmployeeDTO
{
Name = employee.Name,
EmployeeId = employee.EmployeeId
};
// Serialize EmployeeDTO to JSON
string json = JsonSerializer.Serialize(employeeDTO);
Console.WriteLine("Serialized EmployeeDTO:");
Console.WriteLine(json);
// Deserialize JSON back to EmployeeDTO
EmployeeDTO deserializedEmployeeDTO = JsonSerializer.Deserialize<EmployeeDTO>(json);
// Use the deserialized object to create a new Employee object
Employee newEmployee = new Employee
{
Name = deserializedEmployeeDTO.Name,
EmployeeId = deserializedEmployeeDTO.EmployeeId
};
// Use the newEmployee.EmployeeId to find the original Employee object in the Company
Employee foundEmployee = company.Employees.Find(e => e.EmployeeId == newEmployee.EmployeeId);
if (foundEmployee != null)
{
Console.WriteLine("Found Employee:");
Console.WriteLine($"Name: {foundEmployee.Name}");
Console.WriteLine($"Age: {foundEmployee.Age}");
Console.WriteLine($"Gender: {foundEmployee.Gender}");
Console.WriteLine($"Email: {foundEmployee.Email}");
Console.WriteLine($"EmployeeId: {foundEmployee.EmployeeId}");
Console.WriteLine($"Salary: {foundEmployee.Salary}");
Console.WriteLine($"Dependents: {string.Join(", ", foundEmployee.Dependents.Select(d => d.Name))}");
Console.WriteLine($"Emergency Contacts: {string.Join(", ", foundEmployee.EmergencyContacts.Select(ec => ec.Name))}");
}
else
{
Console.WriteLine("Employee not found in the company.");
}
}
}
Neste exemplo, a EmployeeDTO
classe é criada para representar uma versão simplificada da Employee
classe. A EmployeeDTO
classe contém apenas as propriedades necessárias para serialização. A Employee
classe contém propriedades adicionais que não estão incluídas no DTO. A Company
classe contém uma lista de Employee
objetos. O Main
método demonstra como criar um Employee
objeto, mapeá-lo para o EmployeeDTO
, serializar o DTO para JSON e, em seguida, desserializá-lo de volta para o DTO. Por fim, ele mostra como localizar o objeto original Employee
no Company
uso do DTO desserializado.
Essa abordagem permite controlar o processo de serialização e evitar problemas com grafos de objetos complexos. Usando DTOs, você pode simplificar a estrutura de dados e garantir que somente as propriedades necessárias sejam incluídas na saída serializada.
Resumo
Nesta unidade, você aprendeu a gerenciar a serialização e a desserialização de objetos complexos usando a classe e os JsonSerializerOptions
DTOs (Objetos de Transferência de Dados). A JsonSerializerOptions
classe fornece várias propriedades que podem ser personalizadas para lidar com objetos complexos, como MaxDepth
, ReferenceHandler
e Converters
. A ReferenceHandler
propriedade é particularmente útil para lidar com referências circulares em objetos complexos. Os DTOs são objetos intermediários que simplificam a transferência de dados entre diferentes camadas de um aplicativo, permitindo que você controle quais propriedades estão incluídas na saída serializada. Aproveitando essas técnicas, você pode obter serialização e desserialização mais confiáveis e eficientes de dados JSON complexos, facilitando o trabalho e a integração em seus aplicativos.