복잡한 개체의 직렬화 및 역직렬화 관리

완료됨

복잡한 개체 JsonSerializerOptions 를 사용하는 경우 클래스 및 DDO(데이터 전송 개체)를 사용하여 처리 문제를 방지할 수 있습니다. 복잡한 개체는 종종 중첩된 구조체, 특수 데이터 형식을 포함하거나 직렬화 및 역직렬화를 효과적으로 처리하기 위해 특정 구성이 필요합니다. 클래스는 JsonSerializerOptions 이러한 문제를 해결하기 위해 사용자 지정할 수 있는 다양한 속성을 제공합니다. 예를 들어 속성을 사용하여 사용자 지정 변환기를 추가하거나, 대/소문자를 구분하지 않는 속성 일치를 사용하거나, null 값을 처리할 수 있습니다. 반면 DDO는 애플리케이션의 여러 계층 간에 데이터 전송을 간소화하는 중간 개체 역할을 하므로 데이터의 구조와 형식이 일관되고 관리가 가능합니다. 개발자는 DTO를 활용하여 JsonSerializerOptions 복잡한 JSON(JavaScript Object Notation) 데이터를 보다 안정적이고 효율적으로 직렬화하고 역직렬화하여 애플리케이션으로 작업하고 통합할 수 있습니다.

JsonSerializerOptions 클래스를 사용하여 복잡한 개체 직렬화

클래스는 JsonSerializerOptions 네임스페이스의 System.Text.Json 일부이며 JSON 직렬화 및 역직렬화의 동작을 구성하는 방법을 제공합니다. 이를 통해 속성 처리 방법, 참조 관리 방법, 특수 데이터 형식 처리 방법 등 serialization 프로세스의 다양한 측면을 사용자 지정할 수 있습니다.

JsonSerializerOptions 클래스는 중첩된 구조체, 순환 참조 및 기타 serialization 문제를 처리하는 옵션을 제공하므로 복잡한 개체를 처리할 때 유용합니다.

이 단원에서는 다음 속성이 설명되어 있습니다.

  • MaxDepth: 이 속성은 JSON을 읽거나 쓸 때 허용되는 최대 깊이를 설정합니다. 스택 오버플로 또는 성능 문제를 일으킬 수 있는 깊이 중첩된 개체의 문제를 방지하는 데 도움이 될 수 있습니다.
  • ReferenceHandler: 이 속성을 사용하면 serialization 및 deserialization 중에 개체에 대한 참조를 처리하는 방법을 지정할 수 있습니다. 순환 참조 또는 복잡한 개체 그래프를 처리할 때 유용할 수 있습니다.

복합 개체를 직렬화 및 역직렬화하는 데 도움이 되는 또 다른 속성은 .입니다 Converters. 이 속성을 사용 하면 특정 형식 또는 복합 개체를 처리 하는 사용자 지정 변환기를 추가할 수 있습니다. 예를 들어 중첩된 개체 또는 복합 속성을 포함하는 클래스에 대한 사용자 지정 변환기를 만들 수 있습니다.

JsonSerializerOptions.ReferenceHandler 속성 사용

ReferenceHandler 속성은 serialization 및 deserialization 중에 개체에 대한 참조를 처리하는 방법을 지정하는 데 사용됩니다. 이는 순환 참조 또는 공유 참조를 포함할 수 있는 복잡한 개체 그래프를 처리할 때 특히 유용합니다.

순환 참조는 둘 이상의 개체가 서로를 참조하여 루프를 만들 때 발생합니다. 예를 들어 개체를 참조하는 개체 A 가 있고 개체가 개체 BBA를 참조하는 경우 순환 참조가 만들어집니다. 이러한 개체를 직렬화하면 serializer에 무한 루프가 발생하여 스택 오버플로 오류가 발생할 수 있습니다.

두 클래스 간의 순환 참조를 보여 주는 다음 코드 샘플을 고려합니다.


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; }
}

이 예제 Person 에서 클래스에는 개체 목록이 Pet 있으며 각 Pet 개체에는 해당 소유자()에 대한 참조가 Person있습니다. 이렇게 하면 개체를 직렬화하거나 역직렬화할 때 순환 참조가 만들어집니다.

기본 설정으로 개체를 Person 직렬화하면 순환 참조로 인해 발생 JsonException 합니다. 이를 처리하기 위해 클래스의 ReferenceHandlerJsonSerializerOptions 속성을 사용할 수 있습니다.


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"
        }
      }
    ]
  }
}

*/

이 예제에서는 ReferenceHandler.Preserve 이 옵션을 사용하여 순환 참조를 처리합니다. serializer는 JSON 출력에 참조를 나타내는 속성을 포함 $id 하므로 올바르게 역직렬화할 수 있습니다.

역직렬화 ReferenceHandler.Preserve 할 때 이 옵션은 참조가 올바르게 복원되어 중복 개체가 만들어지는 것을 방지합니다.


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

*/

이 예제에서 역직렬화된 Person 개체는 원래 개체와 동일한 참조를 가지며 개체 간의 PersonPet 관계를 유지합니다.

데이터 전송 개체를 사용하여 복잡한 개체 직렬화 및 역직렬화

데이터 전송 개체는 복잡한 개체를 직렬화하고 역직렬화해야 하는 시나리오에서 자주 사용됩니다.

DDO는 다음과 같은 이점을 제공합니다.

  • 간소화된 데이터 구조: DDO는 중첩된 개체를 평면화하거나 불필요한 속성을 제거하여 데이터 구조를 간소화할 수 있습니다. 이렇게 하면 복잡한 개체 그래프를 처리하지 않고도 데이터를 보다 쉽게 직렬화하고 역직렬화할 수 있습니다.
  • 선택적 serialization: DDO를 사용하면 직렬화된 출력에 포함되는 속성을 제어할 수 있습니다. 이렇게 하면 직렬화된 데이터의 크기를 줄이고 성능을 향상시킬 수 있습니다.
  • 분리: DDO는 비즈니스 논리에서 데이터 구조를 분리하여 코드를 보다 쉽게 관리하고 유지 관리합니다. 이러한 문제를 분리하면 애플리케이션의 나머지 부분에 영향을 주지 않고 serialization 형식을 더 쉽게 변경할 수 있습니다.
  • 데이터 계약: DDO는 데이터 계약으로 작동하여 직렬화되고 역직렬화된 데이터의 구조를 정의합니다. 이렇게 하면 애플리케이션의 여러 부분 또는 다른 애플리케이션 간에 일관성과 호환성을 보다 쉽게 보장할 수 있습니다.
  • 상호 운용성: DDO를 사용하면 데이터 구조가 다른 시스템 또는 서비스와 호환되도록 하여 외부 API 또는 서비스와 쉽게 통합할 수 있습니다.
  • 버전 관리: DDO는 데이터 구조의 버전 관리를 지원하여 기존 클라이언트 또는 서비스를 중단하지 않고도 데이터 모델을 발전시킬 수 있습니다.
  • 보안: DDO는 내부 데이터 구조의 노출을 제한하여 보안을 개선하는 데 도움이 될 수 있습니다. DDO를 사용하여 직렬화 및 역직렬화되는 속성을 제어하여 중요한 정보를 노출할 위험을 줄일 수 있습니다.
  • 성능: DDO는 네트워크를 통해 전송되는 데이터의 양을 줄여 성능을 향상시킬 수 있습니다. DDO를 사용하면 필요한 속성만 직렬화하여 직렬화된 데이터의 크기를 줄이고 성능을 향상시킬 수 있습니다.

직렬화하려는 속성만 포함하는 사용자 지정 DTO를 만들어 개체의 일부를 선택적으로 직렬화할 수 있습니다. 이 방법을 사용하면 중첩된 개체를 포함하지 않고 직렬화되는 항목을 정확하게 제어할 수 있습니다.

직렬화에 DDO 만들기 및 사용

다음은 C#에서 직렬화에 DDO를 만들고 사용하는 방법에 대한 단계별 가이드입니다.

  • DTO 정의: 직렬화하려는 속성 및 메서드 결과만 포함하는 클래스를 만듭니다.
  • 원래 개체를 DTO에 매핑: 원래 개체를 DTO에 매핑하는 메서드를 만듭니다. 이 메서드는 원래 개체에서 필요한 데이터를 추출하고 DTO를 채웁니다.
  • DTO 직렬화: 클래스를 JsonSerializer 사용하여 DTO를 JSON 문자열로 직렬화합니다.
  • DTO 역직렬화: 클래스를 JsonSerializer 사용하여 JSON 문자열을 DTO로 다시 역직렬화합니다.
  • DTO를 원래 개체에 다시 매핑: DTO를 원래 개체에 다시 매핑하는 메서드를 만듭니다. 이 메서드는 DTO에서 데이터를 추출하고 원래 개체를 채웁니다.

다음 예제에서는 복잡한 개체의 직렬화 및 역직렬화에 DTO를 만들고 사용하는 방법을 보여 줍니다.


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.");
        }
    }
}

이 예제에서는 클래스의 EmployeeDTO 간소화된 버전을 나타내기 위해 클래스가 Employee 만들어집니다. 클래스에는 EmployeeDTO serialization에 필요한 속성만 포함됩니다. 클래스에는 Employee DTO에 포함되지 않은 추가 속성이 포함되어 있습니다. 클래스에는 Company 개체 목록이 Employee 포함되어 있습니다. 이 메서드는 Main 개체를 Employee 만들고, 개체에 EmployeeDTO매핑하고, DTO를 JSON으로 직렬화한 다음, DTO로 다시 역직렬화하는 방법을 보여 줍니다. 마지막으로 역직렬화된 DTO를 사용하여 원래 Employee 개체를 Company 찾는 방법을 보여줍니다.

이 방법을 사용하면 serialization 프로세스를 제어하고 복잡한 개체 그래프 문제를 방지할 수 있습니다. DDO를 사용하면 데이터 구조를 간소화하고 필요한 속성만 직렬화된 출력에 포함되도록 할 수 있습니다.

요약

이 단원에서는 클래스 및 DDO(데이터 전송 개체)를 사용하여 JsonSerializerOptions 복잡한 개체의 직렬화 및 역직렬화를 관리하는 방법을 알아보았습니다. 클래스는 JsonSerializerOptions 복잡한 개체(예 MaxDepth: , ReferenceHandlerConverters)를 처리하도록 사용자 지정할 수 있는 다양한 속성을 제공합니다. 이 ReferenceHandler 속성은 복잡한 개체에서 순환 참조를 처리하는 데 특히 유용합니다. DDO는 애플리케이션의 여러 계층 간에 데이터 전송을 간소화하는 중간 개체이므로 직렬화된 출력에 포함되는 속성을 제어할 수 있습니다. 이러한 기술을 활용하여 복잡한 JSON 데이터를 보다 안정적이고 효율적으로 직렬화하고 역직렬화하여 애플리케이션으로 작업하고 통합하기 쉽습니다.