CodeGym /행동 /C# SELF /System.Text.Json으로 직렬화하기 <...

System.Text.Json으로 직렬화하기

C# SELF
레벨 44, 레슨 3
사용 가능

1. 소개

오랫동안 .NET 생태계에서 JSON 작업의 주된 도구는 훌륭한 서드파티 패키지인 Newtonsoft.Json(또는 Json.NET)이었어요. 강력하고 유연해서 지금도 많이 쓰입니다. 하지만 .NET 9와 C# 14가 나오면서 Microsoft는 자체적으로 내장된 고성능 JSON 직렬화기를 제공하기로 했습니다. 그래서 등장한 것이 System.Text.Json입니다.

왜 새로운 접근이 필요했을까요? System.Text.Json은 현대적인 요구를 반영해 설계되었고, 오랫동안 서드파티 라이브러리 사용 중 쌓인 문제들을 해결하려고 합니다. 성능과 안전성에 최적화되어 있고 비동기 시나리오와 웹 API에 적합하며 — 가장 좋은 점은 — NuGet으로 설치할 필요가 없다는 거죠: 플랫폼에 이미 포함되어 있습니다.

물론 Newtonsoft.Json은 여전히 유효하고 나중에 다뤄볼 거예요. 하지만 대부분의 신규 프로젝트에서는 System.Text.Json이 기본 선택입니다. 자, 이제 객체들을 JSON 언어로 말하게 해봅시다!

2. System.Text.Json의 기본 사용법

객체의 간단한 직렬화

자, 기본 매직부터 시작합시다. 객체를 JSON 문자열로 직렬화해볼게요.

using System;
using System.Text.Json; // 잊지 말고 추가!

public class Player
{
    public string Name { get; set; }
    public int Health { get; set; }
    public bool IsAlive { get; set; }
}

// 프로그램 어딘가에서:
Player player = new Player { Name = "Aragorn", Health = 100, IsAlive = true };

// 직렬화:
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // 출력: {"Name":"Aragorn","Health":100,"IsAlive":true}

코멘트: 직렬화를 막 배우는 사람에게 이 예제는 얼마나 간단한지 보여줍니다: JsonSerializer.Serialize — 끝.

객체의 역직렬화

JSON 문자열에서 객체를 복원해봅시다:

string incomingJson = "{\"Name\":\"Legolas\",\"Health\":88,\"IsAlive\":true}";

Player player2 = JsonSerializer.Deserialize<Player>(incomingJson);
Console.WriteLine(player2.Name);   // Legolas
Console.WriteLine(player2.Health); // 88
Console.WriteLine(player2.IsAlive); // true

코멘트: JSON 구조가 클래스와 일치하면 — 문제없이 작동합니다. 그렇지 않으면 예외가 발생하거나 기본값이 들어갈 수 있어요.

3. 직렬화의 원리와 동작 방식

매핑이 어떻게 이뤄지는가

System.Text.Json은 기본적으로 클래스의 속성 이름을 그대로 사용합니다. 대소문자를 구분해요! 만약 JSON에 health라고 되어 있고 클래스에는 Health라면 역직렬화가 실패하고 — 해당 속성은 기본값(0, false 또는 null)을 유지합니다.

예:

// 소문자 키를 가진 JSON:
string badJson = "{\"name\":\"Gimli\",\"health\":120,\"isAlive\":true}";
Player player3 = JsonSerializer.Deserialize<Player>(badJson);
Console.WriteLine(player3.Name);    // 비어있음
Console.WriteLine(player3.Health);  // 0
Console.WriteLine(player3.IsAlive); // false

재미있는 사실: 많은 API가 키를 camelCase(예: health)로 쓰고, C# 쪽은 보통 PascalCase(예: Health)를 씁니다. 이 문제는 설정으로 해결할 수 있어요(아래 참조).

4. 직렬화 제어 — 옵션과 설정

JSON 포맷팅: 사람이 읽기 좋은 출력

가끔은 압축된 JSON 대신 보기 좋게 들여쓰기된 JSON이 필요하죠 — 설정 파일이나 로그용으로요.

var options = new JsonSerializerOptions
{
    WriteIndented = true // 들여쓰기 추가
};

string prettyJson = JsonSerializer.Serialize(player, options);
Console.WriteLine(prettyJson);
/*
{
  "Name": "Frodo",
  "Health": 50,
  "IsAlive": true,
  "Inventory": [
    "Ring",
    "Bread",
    "Torch"
  ],
  "Position": {
    "X": 5,
    "Y": 15
  }
}
*/

WriteIndented 속성을 켜면 직렬화기는 들여쓰기와 줄바꿈을 추가합니다.

이름 스타일 제어 (CamelCase vs PascalCase)

웹 API에서 키가 모두 camelCase라면 네이밍 정책을 활성화하세요:

var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

string camelCaseJson = JsonSerializer.Serialize(player, options);
// {"name":"Frodo","health":50,"isAlive":true,"inventory":["Ring","Bread","Torch"],"position":{"x":5,"y":15}}

이렇게 하면 역직렬화 시에도 camelCase 키가 올바르게 매핑됩니다:

string apiJson = "{\"name\":\"Bilbo\",\"health\":40,\"isAlive\":true,\"inventory\":[\"Mug\"],\"position\":{\"x\":10,\"y\":5}}";
Player bilbo = JsonSerializer.Deserialize<Player>(apiJson, options);
Console.WriteLine(bilbo.Name); // Bilbo

5. 유용한 뉘앙스

[JsonIgnore] 어트리뷰트 사용

모든 속성을 직렬화할 필요는 없죠 — 예를 들어 민감한 데이터나 임시 계산값은 제외하고 싶을 때가 있습니다.

using System.Text.Json.Serialization;

public class Player
{
    public string Name { get; set; }
    public int Health { get; set; }

    [JsonIgnore] // 이 속성은 JSON에 포함되지 않음
    public bool IsSecretCharacter { get; set; }
}

이제 직렬화하면:

var player = new Player { Name = "Boromir", Health = 80, IsSecretCharacter = true };
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // {"Name":"Boromir","Health":80}

역직렬화 시 IsSecretCharacter는 기본값(false)을 가지게 됩니다.

[JsonPropertyName("...")] 사용하기

코드에서 속성 이름이 IsAlive인데 JSON에서 "status"로 나오게 하고 싶다면:

using System.Text.Json.Serialization;

public class Player
{
    public string Name { get; set; }
    public int Health { get; set; }

    [JsonPropertyName("status")]
    public bool IsAlive { get; set; }
}

직렬화 결과는 이렇게 됩니다:

var player = new Player { Name = "Pippin", Health = 60, IsAlive = false };
string json = JsonSerializer.Serialize(player);
Console.WriteLine(json); // {"Name":"Pippin","Health":60,"status":false}

역직렬화 시에도 "status" 키가 IsAlive 속성을 제대로 채워줍니다.

내장 제약과 보안 관련 특징

  • 기본적으로 public getter/setter가 있는 public 속성만 직렬화됩니다; private 필드/속성은 무시됩니다.
  • 순환 참조가 있으면 예외가 발생합니다: "A possible object cycle was detected".
  • Newtonsoft.Json과 달리 기본 직렬화기는 타입에 대한 '매직'한 트릭에 덜 의존합니다 — 덕분에 전형적인 시나리오에서는 더 안전하고 빠릅니다.

6. 흔한 실수와 함정

JSON에서 속성 이름을 실수로 변경하고 코드를 업데이트하지 않으면 — 해당 속성은 기본값(null, 0, false)을 가지게 됩니다.

JSON에 필요한 필드가 빠져 있으면 — 대응하는 객체 속성은 기본값이 됩니다 (자세한 내용은 문서 참조).

속성에 public setter가 없으면 — 역직렬화 시 값이 채워지지 않습니다.

중첩된 클래스나 컬렉션 구조를 변경하면 — 역직렬화가 깨지거나 예상치 못한 결과가 나올 수 있습니다.

부모와 자식 객체의 서로 다른 레벨에서 같은 이름을 쓰면 디버깅이 어려워집니다.

가끔 문제는 플랫폼 버전 차이입니다: 옛 버전의 System.Text.Json은 일부 타입(예: Dictionary, DateTime, enum)을 덜 잘 다뤘지만 .NET 7/8/9에서는 많은 부분이 개선되었습니다.

2
과제
C# SELF, 레벨 44, 레슨 3
잠금
JsonPropertyName을 사용한 역직렬화
JsonPropertyName을 사용한 역직렬화
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION