Serialization(객체를 직렬화/역직렬화)

00·2025년 1월 1일

C#

목록 보기
109/149
using System;
using System.IO; // 파일 입출력을 위한 네임스페이스
using System.Text.Json; // JSON 직렬화 및 역직렬화를 위한 네임스페이스
                        // JsonSerializer 클래스가 소속되어 있는 네임스페이스


/*
직렬화(Serialization):
객체의 상태(여기서는 객체의 상태란 객체의 필드에 저장된 값을 말함)를 
메모리나 영구 저장 장치에 저장이 가능한 0과 1의 순서로 바꾸는 것.


직렬화(Serialization) 매커니즘 나온 배경:
BinaryWriter/BinaryReader 클래스와 StreamWriter/StreamReader 클래스는 
기본 데이터 형식을 스트림에 쓰고 읽을 수 있도록 메서드를 제공하지만, 
프로그래머가 직접 정의한 클래스나 복합 데이터 형식(예를 들어 구조체)은 지원하지 않습니다.
따라서 BinaryWriter/BinaryReader나 StreamWriter/StreamReader로 복합 데이터 형식을 쓰고 읽으려면,
그 형식이 가진 필드의 값을 저장할 순서를 정한 후에, 이 '순서대로' 저장하고 읽는 코드를 작성해야 하는 문제가 발생합니다.
C#은 이를 '한 번에' 저장할 수 있도록 복합 데이터 형식을 스트림에 쉽게 쓰고 읽을 수 있게 하는 '직렬화'라는 매커니즘을 제공합니다.


JsonSerializer 클래스:
System.Text.Json 네임스페이스에 소속되어 있고,
객체를 JSON 형식으로 직렬화하거나 역직렬화합니다.
클래스 안에 어떤 프로퍼티들이 어떻게 선언되어있는지 고민할 필요 없이,
JsonSerializer에게 맡기면 객체의 직렬화든 역질화든 알아서 해줍니다.
 */


// System.Text.Json 네임스페이스의 JsonSerializer 클래스를 사용하여,
// NameCard 객체를 JSON 형식으로 파일에 쓰고 읽는 예제입니다.
namespace Serialization
{
    class NameCard // NameCard 클래스 정의
    {
        public string Name { get; set; } // 이름을 저장할 Name 프로퍼티 선언
        public string Phone { get; set; } // 전화번호를 저장할 Phone 프로퍼티 선언
        public int Age { get; set; } // 나이를 저장할 Age 프로퍼티 선언
    }


    class MainApp
    {
        static void Main(string[] args)
        {
            var fileName = "a.json"; // 파일 이름을 "a.json"으로 설정

            using (Stream ws = new FileStream(fileName, FileMode.Create)) // "a.json" 파일을 쓰기 모드로 열거나 생성.
                                                                          // using 문을 사용하여 ws 객체를 사용한 후
                                                                          // 자동으로 Close() 메서드가 호출되어 파일이 닫힙니다.
            {

                NameCard nc = new NameCard() // NameCard 객체를 생성하고, 프로퍼티 값을 설정합니다.
                {
                    Name = "박상현", // Name 프로퍼티에 "박상현" 저장
                    Phone = "010-123-4567", // Phone 프로퍼티에 "010-123-4567" 저장
                    Age = 33 // Age 프로퍼티에 33 저장
                };

                string jsonString = JsonSerializer.Serialize<NameCard>(nc); // NameCard 객체를 JSON 문자열로 '직렬화'

                byte[] jsonBytes = System.Text.Encoding.UTF8.GetBytes(jsonString); // JSON 문자열을 UTF-8 인코딩으로 바이트 배열로 변환
                
                ws.Write(jsonBytes, 0, jsonBytes.Length); // 바이트 배열을 파일에 씀

            } // using 블록 종료 시 ws.Close() 자동 호출


            using (Stream rs = new FileStream(fileName, FileMode.Open)) // "a.json" 파일을 읽기 모드로 염
            {
                byte[] jsonBytes = new byte[rs.Length]; // 파일에서 읽어온 데이터를 저장할 바이트 배열 jsonBytes 선언
                rs.Read(jsonBytes, 0, jsonBytes.Length);  // 파일에서 바이트 배열을 읽어옴
                string jsonString = System.Text.Encoding.UTF8.GetString(jsonBytes); // 바이트 배열을 UTF-8 인코딩으로 문자열로 변환

                NameCard nc2 = JsonSerializer.Deserialize<NameCard>(jsonString); // JSON 문자열을 NameCard 객체로 '역직렬화'

                Console.WriteLine($"Name:  {nc2.Name}"); // NameCard 객체의 Name 프로퍼티 값 출력
                Console.WriteLine($"Phone: {nc2.Phone}"); // NameCard 객체의 Phone 프로퍼티 값 출력
                Console.WriteLine($"Age:   {nc2.Age}"); // NameCard 객체의 Age 프로퍼티 값 출력
            } // using 블록 종료 시 rs.Close() 자동 호출
        }
    }
}


/*
출력 결과

Name:  박상현
Phone: 010-123-4567
Age:   33
*/

코드 설명

using System;
using System.IO; // 파일 입출력을 위한 네임스페이스
using System.Text.Json; // JSON 직렬화 및 역직렬화를 위한 네임스페이스

namespace Serialization
{
    class NameCard // NameCard 클래스 정의
    {
        public string Name { get; set; } // 이름을 저장할 Name 프로퍼티
        public string Phone { get; set; } // 전화번호를 저장할 Phone 프로퍼티
        public int Age { get; set; } // 나이를 저장할 Age 프로퍼티
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            var fileName = "a.json"; // 파일 이름을 "a.json"으로 설정

            using (Stream ws = new FileStream(fileName, FileMode.Create)) // "a.json" 파일을 쓰기 모드로 열거나 생성
            {
                NameCard nc = new NameCard() // NameCard 객체 생성
                {
                    Name = "박상현", // Name 프로퍼티에 "박상현" 저장
                    Phone = "010-123-4567", // Phone 프로퍼티에 "010-123-4567" 저장
                    Age = 33 // Age 프로퍼티에 33 저장
                };

                string jsonString = JsonSerializer.Serialize<NameCard>(nc); // NameCard 객체를 JSON 문자열로 직렬화
                byte[] jsonBytes = System.Text.Encoding.UTF8.GetBytes(jsonString); // JSON 문자열을 UTF-8 인코딩으로 바이트 배열로 변환
                ws.Write(jsonBytes, 0, jsonBytes.Length); // 바이트 배열을 파일에 씀
            } // using 블록 종료 시 ws.Close() 자동 호출

            using (Stream rs = new FileStream(fileName, FileMode.Open)) // "a.json" 파일을 읽기 모드로 염
            {
                byte[] jsonBytes = new byte[rs.Length]; // 파일에서 읽어온 데이터를 저장할 바이트 배열 jsonBytes 선언
                rs.Read(jsonBytes, 0, jsonBytes.Length); // 파일에서 바이트 배열을 읽어옴
                string jsonString = System.Text.Encoding.UTF8.GetString(jsonBytes); // 바이트 배열을 UTF-8 인코딩으로 문자열로 변환

                NameCard nc2 = JsonSerializer.Deserialize<NameCard>(jsonString); // JSON 문자열을 NameCard 객체로 역직렬화

                Console.WriteLine($"Name:  {nc2.Name}"); // NameCard 객체의 Name 프로퍼티 값 출력
                Console.WriteLine($"Phone: {nc2.Phone}"); // NameCard 객체의 Phone 프로퍼티 값 출력
                Console.WriteLine($"Age:   {nc2.Age}"); // NameCard 객체의 Age 프로퍼티 값 출력
            } // using 블록 종료 시 rs.Close() 자동 호출
        }
    }
}

코드 설명

이 C# 코드는 System.Text.Json 네임스페이스의 JsonSerializer 클래스를 사용하여 NameCard 객체를 JSON 형식으로 파일에 쓰고 읽는 예제입니다.

  • NameCard 클래스: 이름, 전화번호, 나이를 저장하는 프로퍼티를 가진 클래스입니다.
  • Main 메서드:
    • var fileName = "a.json";: 파일 이름을 "a.json"으로 설정합니다.
    • using (Stream ws = new FileStream(fileName, FileMode.Create)): "a.json" 파일을 쓰기 모드로 열거나 생성합니다. using 문을 사용하면 ws 객체를 사용한 후 자동으로 Close() 메서드가 호출되어 파일이 닫힙니다.
    • NameCard nc = new NameCard() { ... };: NameCard 객체를 생성하고 프로퍼티 값을 설정합니다.
    • string jsonString = JsonSerializer.Serialize<NameCard>(nc);: NameCard 객체를 JSON 문자열로 직렬화합니다.
    • byte[] jsonBytes = System.Text.Encoding.UTF8.GetBytes(jsonString);: JSON 문자열을 UTF-8 인코딩으로 바이트 배열로 변환합니다.
    • ws.Write(jsonBytes, 0, jsonBytes.Length);: 바이트 배열을 파일에 씁니다.
    • using (Stream rs = new FileStream(fileName, FileMode.Open)): "a.json" 파일을 읽기 모드로 엽니다.
    • byte[] jsonBytes = new byte[rs.Length];: 파일에서 읽어온 데이터를 저장할 바이트 배열 jsonBytes를 선언합니다.
    • rs.Read(jsonBytes, 0, jsonBytes.Length);: 파일에서 바이트 배열을 읽어옵니다.
    • string jsonString = System.Text.Encoding.UTF8.GetString(jsonBytes);: 바이트 배열을 UTF-8 인코딩으로 문자열로 변환합니다.
    • NameCard nc2 = JsonSerializer.Deserialize<NameCard>(jsonString);: JSON 문자열을 NameCard 객체로 역직렬화합니다.
    • Console.WriteLine($"Name: {nc2.Name}");, Console.WriteLine($"Phone: {nc2.Phone}");, Console.WriteLine($"Age: {nc2.Age}");: NameCard 객체의 프로퍼티 값을 출력합니다.

출력 결과

Name:  박상현
Phone: 010-123-4567
Age:   33

직렬화, 역직렬화

직렬화와 역직렬화는 객체를 저장하거나 전송하기 위해 객체의 데이터를 특정 형식으로 변환하는 과정입니다. 마치 택배를 보내기 위해 상자에 물건을 담는 것과 같습니다.

직렬화(Serialization)는 객체를 바이트 스트림 또는 텍스트 형식으로 변환하는 과정입니다. 이렇게 변환된 데이터는 파일로 저장하거나 네트워크를 통해 전송할 수 있습니다.

역직렬화(Deserialization)는 직렬화된 데이터를 다시 객체로 변환하는 과정입니다. 즉, 바이트 스트림 또는 텍스트 형식의 데이터를 읽어서 원래의 객체를 복원합니다.

직렬화의 용도

  • 객체를 파일에 저장하여 나중에 다시 사용할 수 있도록 합니다.
  • 객체를 네트워크를 통해 다른 컴퓨터로 전송할 수 있도록 합니다.
  • 객체를 세션에 저장하여 웹 애플리케이션에서 상태를 유지할 수 있도록 합니다.

역직렬화의 용도

  • 파일에 저장된 객체를 다시 사용할 수 있도록 합니다.
  • 네트워크를 통해 전송된 객체를 다시 사용할 수 있도록 합니다.
  • 세션에 저장된 객체를 다시 사용할 수 있도록 합니다.

직렬화 및 역직렬화를 지원하는 형식

  • JSON (JavaScript Object Notation)
  • XML (Extensible Markup Language)
  • 바이너리 형식

C#에서 직렬화 및 역직렬화

C#에서는 System.Text.Json 네임스페이스의 JsonSerializer 클래스를 사용하여 JSON 형식으로 객체를 직렬화하고 역직렬화할 수 있습니다.

예시

// 객체 생성
Person person = new Person { Name = "John Doe", Age = 30 };

// 객체를 JSON 문자열로 직렬화
string jsonString = JsonSerializer.Serialize(person);

// JSON 문자열을 객체로 역직렬화
Person deserializedPerson = JsonSerializer.Deserialize<Person>(jsonString);

이 코드는 Person 객체를 JSON 문자열로 직렬화하고, 다시 Person 객체로 역직렬화하는 예제입니다.


BinaryWriter/BinaryReader 클래스 vs StreamWriter/StreamReader 클래스 vs 직렬화

BinaryWriter/BinaryReader 클래스와 StreamWriter/StreamReader 클래스, 그리고 직렬화는 모두 데이터를 파일에 쓰고 읽는 데 사용되지만, 몇 가지 중요한 차이점이 있습니다.

1. 데이터 형식

  • BinaryWriter/BinaryReader: 이진 형식으로 데이터를 쓰고 읽습니다. 즉, 데이터를 바이트 스트림으로 변환하여 파일에 저장합니다.
  • StreamWriter/StreamReader: 텍스트 형식으로 데이터를 쓰고 읽습니다. 즉, 데이터를 문자열로 변환하여 파일에 저장합니다.
  • 직렬화: 객체를 바이트 스트림이나 텍스트 형식으로 변환합니다. JSON, XML, 바이너리 등 다양한 형식을 사용할 수 있습니다.

2. 사용 용도

  • BinaryWriter/BinaryReader: 숫자, 문자열, 불리언 값 등 기본 데이터 타입을 파일에 저장하고 읽을 때 사용합니다.
  • StreamWriter/StreamReader: 텍스트 파일을 읽고 쓸 때 사용합니다. 예를 들어, 로그 파일, 설정 파일, CSV 파일 등을 읽고 쓸 때 사용합니다.
  • 직렬화: 객체의 상태를 저장하고 복원할 때 사용합니다. 예를 들어, 객체를 파일에 저장하거나 네트워크를 통해 전송할 때 사용합니다.

3. 성능

  • BinaryWriter/BinaryReader: 이진 형식으로 데이터를 저장하기 때문에 StreamWriter/StreamReader보다 일반적으로 성능이 더 좋습니다.
  • StreamWriter/StreamReader: 텍스트 형식으로 데이터를 저장하기 때문에 BinaryWriter/BinaryReader보다 일반적으로 성능이 떨어집니다.
  • 직렬화: 직렬화 형식에 따라 성능이 다릅니다. 일반적으로 이진 형식이 텍스트 형식보다 성능이 더 좋습니다.

4. 가독성

  • BinaryWriter/BinaryReader: 이진 형식으로 저장된 데이터는 사람이 읽기 어렵습니다.
  • StreamWriter/StreamReader: 텍스트 형식으로 저장된 데이터는 사람이 읽기 쉽습니다.
  • 직렬화: 직렬화 형식에 따라 가독성이 다릅니다. 일반적으로 텍스트 형식이 이진 형식보다 가독성이 더 좋습니다.

요약

특징BinaryWriter/BinaryReaderStreamWriter/StreamReader직렬화
데이터 형식이진 형식텍스트 형식바이트 스트림 또는 텍스트 형식
사용 용도기본 데이터 타입 저장 및 읽기텍스트 파일 읽기 및 쓰기객체 상태 저장 및 복원
성능일반적으로 더 좋음일반적으로 떨어짐직렬화 형식에 따라 다름
가독성사람이 읽기 어려움사람이 읽기 쉬움직렬화 형식에 따라 다름

어떤 방식을 사용할지는 데이터의 종류, 용도, 성능 요구 사항, 가독성 등을 고려하여 결정해야 합니다.


직렬화 나온 배경

직렬화는 복합 데이터 형식을 다루기 쉽게 해주는 기능이라고 할 수 있습니다.

BinaryWriter/BinaryReaderStreamWriter/StreamReader를 사용하면 데이터를 파일에 저장하고 읽을 수 있지만, 복잡한 데이터를 다룰 때는 불편한 점이 있습니다. 예를 들어, 사람의 정보를 저장하려면 이름, 나이, 주소 등 여러 개의 데이터를 저장해야 하는데, 이러한 데이터를 파일에 저장하려면 각 데이터를 어떤 순서로 저장할지, 어떤 형식으로 저장할지 등을 일일이 지정해야 합니다.

직렬화는 이러한 복잡한 과정을 단순화해줍니다. 직렬화를 사용하면 객체를 통째로 파일에 저장하고, 다시 읽어올 수 있습니다. 마치 냉장고에 음식을 보관하는 것과 같습니다. 음식을 냉장고에 넣으면, 음식의 모양, 맛, 영양 성분 등 모든 정보가 그대로 보존됩니다. 마찬가지로 객체를 직렬화하면 객체의 모든 데이터가 그대로 보존됩니다.

직렬화된 데이터 형식

직렬화된 데이터는 바이트 스트림 또는 텍스트 형식입니다.

  • 바이트 스트림은 컴퓨터가 이해할 수 있는 0과 1로 이루어진 데이터입니다.
  • 텍스트 형식은 사람이 읽을 수 있는 문자열로 이루어진 데이터입니다.

JSON(JavaScript Object Notation)과 XML(Extensible Markup Language)은 텍스트 형식의 직렬화 형식입니다.

직렬화의 사용 용도

직렬화는 객체의 상태를 저장하고 복원하는 데 사용됩니다.

  • 객체를 파일에 저장: 객체를 직렬화하여 파일에 저장하면, 나중에 파일에서 객체를 다시 읽어와서 사용할 수 있습니다.
  • 객체를 네트워크를 통해 전송: 객체를 직렬화하여 네트워크를 통해 전송하면, 다른 컴퓨터에서 객체를 다시 사용할 수 있습니다.

직렬화의 장점

  • 복잡한 데이터를 쉽게 저장하고 복원할 수 있습니다.
  • 객체의 상태를 그대로 보존할 수 있습니다.
  • 다양한 형식으로 직렬화할 수 있습니다.

직렬화는 객체를 다루는 데 매우 유용한 기능입니다.

직렬화 나온 배경

직렬화는 복잡한 데이터를 쉽게 저장하고 불러올 수 있도록 C#에서 제공하는 기능입니다.

BinaryWriter/BinaryReaderStreamWriter/StreamReader는 데이터를 파일에 저장하고 읽을 수 있지만, 복잡한 데이터를 다룰 때는 불편합니다. 예를 들어, 게임 캐릭터 정보를 저장한다고 생각해 봅시다. 캐릭터의 이름, 레벨, 능력치, 아이템 등 여러 가지 데이터가 있을 텐데, 이러한 데이터를 파일에 저장하려면 각 데이터를 어떤 순서로 저장할지, 어떤 형식으로 저장할지 등을 일일이 지정해야 합니다.

직렬화는 이러한 복잡한 과정을 단순화해줍니다. 직렬화를 사용하면 객체를 통째로 파일에 저장하고, 다시 읽어올 수 있습니다. 마치 게임을 저장하는 것과 같습니다. 게임을 저장하면 캐릭터의 정보, 아이템, 진행 상황 등 모든 정보가 그대로 저장됩니다. 마찬가지로 객체를 직렬화하면 객체의 모든 데이터가 그대로 저장됩니다.

직렬화를 사용하면 개발자는 복잡한 데이터 구조를 직접 처리할 필요 없이, 객체를 간편하게 저장하고 불러올 수 있습니다. 덕분에 코드가 더 간결해지고 유지보수도 쉬워집니다.

직렬화는 다양한 분야에서 사용됩니다. 예를 들어, 게임 데이터 저장, 설정 파일 저장, 웹 서비스 데이터 전송 등에 활용됩니다.

0개의 댓글