CodeGym /행동 /C# SELF /직렬화 설정 ( JsonSerializerOpti...

직렬화 설정 ( JsonSerializerOptions)

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

1. 소개

기본 동작이 필요에 맞으면 좋습니다. 하지만 종종 클라이언트(또는 서드파티 백엔드)가 엄격한 포맷을 요구합니다: everywhere camelCase, 날짜 — ISO 8601, null 건너뛰기, 커스텀 컨버터 등 통합자가 좋아하는 요구사항들.

여기서 우리의 새 친구 — 클래스 JsonSerializerOptions가 등장합니다.

직렬화를 주방에 비유하면, 이 클래스는 당신의 향신료, 냄비, 비밀 재료로써 직렬화를 원하는 대로 맛내게 해줍니다.

JsonSerializerOptions 사용법

직렬화나 역직렬화 과정을 커스터마이즈하려면, 설정 객체를 직렬화 메서드에 전달하면 됩니다:

using System.Text.Json;

var options = new JsonSerializerOptions
{
    // 여기에 설정을 넣으면 됨
};

string json = JsonSerializer.Serialize(myObject, options);
var obj = JsonSerializer.Deserialize<MyType>(json, options);

이 패턴은 어디서나 보입니다: JsonSerializerOptions를 만들고 취향대로 설정한 다음(아래 참조) 직렬화 메서드 Serialize/Deserialize에 넘깁니다.

중요: 같은 JsonSerializerOptions 객체는 여러 직렬화에서 재사용할 수(그리고 재사용하는게 권장됨) 있습니다.

2. 주요 설정들 — JsonSerializerOptions

이 클래스의 가장 흔히 쓰이는 "다이얼"과 "스위치"들을 살펴봅시다.

JSON 포맷팅 — WriteIndented

한 줄짜리 JSON이 눈에 거슬리나요? 걱정 마세요 — 들여쓰기 있는 보기 좋은 포맷을 켤 수 있습니다!

var options = new JsonSerializerOptions
{
    WriteIndented = true // 보기 좋은 "사람 친화적" JSON
};

var person = new Person { Name = "Anna", Age = 30 };
string json = JsonSerializer.Serialize(person, options);

Console.WriteLine(json);

출력:

{
  "Name": "Anna",
  "Age": 30
}

WriteIndented = true가 없으면 밋밋한 {"Name":"Anna","Age":30}가 나옵니다. 프로덕션에서는 보통 미니파이된 JSON(용량 절약)을 쓰고, 디버그나 로그용으로 포맷된 JSON을 씁니다.

네이밍 케이스 — PropertyNamingPolicy

많은 API가 속성 이름을 camelCase(예: firstName)로 요구합니다. .NET에서는 보통 PascalCase를 쓰므로 PropertyNamingPolicy가 유용합니다:

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

var person = new Person { Name = "Oleg", Age = 25 };
string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json); // {"name":"Oleg","age":25}

모든 문자를 대문자로 하는 등 특별한 스타일이 필요하면 직접 JsonNamingPolicy를 구현하면 됩니다. 보통은 CamelCase로 충분합니다.

3. 속성 무시하기

null 값을 건너뛸지 말지 — DefaultIgnoreCondition

때때로 값이 비어있는(null) 필드를 JSON에서 완전히 빼고 싶습니다. 이유는 더 컴팩트한 JSON이 필요하거나 외부 시스템이 null을 못 받아서일 수 있습니다.

이럴 때는 DefaultIgnoreCondition을 사용합니다:

using System.Text.Json.Serialization;

var options = new JsonSerializerOptions
{
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

var person = new Person { Name = null, Age = 22 };
string json = JsonSerializer.Serialize(person, options);
Console.WriteLine(json); // {"Age":22}

WhenWritingDefault를 쓰면 null뿐만 아니라 기본값들(예: int=0)도 무시됩니다:

options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault;

private 속성 무시/포함 — IncludeFields[JsonInclude]

기본적으로는 public 속성(와 공개 setter)을 직렬화합니다. 필드(fields)도 직렬화하고 싶다면 다음을 켭니다:

var options = new JsonSerializerOptions
{
    IncludeFields = true
};

클래스 예시:

public class Item
{
    public string Name;
    public int Id { get; set; }
}

var item = new Item { Name = "Book", Id = 12 };
string json = JsonSerializer.Serialize(item, options);
// {"Name":"Book","Id":12}

private 필드/속성을 직렬화하려면 [JsonInclude] 어트리뷰트를 쓸 수 있지만, 그건 별도 강의 주제입니다.

4. 값 변환

날짜 포맷 — Converters

기본적으로 System.Text.Json은 날짜를 ISO 8601(예: "2023-12-27T15:30:45.123Z") 형식으로 직렬화합니다. 보통 편리하지만, 때로는 다른 형식이 필요할 수 있습니다.

이럴 때는 커스텀 컨버터를 추가합니다. 간단한 예:

using System.Text.Json;
using System.Text.Json.Serialization;

public class CustomDateTimeConverter : JsonConverter<DateTime>
{
    private string _format = "yyyyMMdd";

    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var date = reader.GetString();
        return DateTime.ParseExact(date, _format, null);
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString(_format));
    }
}

// 컨버터 사용
var options = new JsonSerializerOptions();
options.Converters.Add(new CustomDateTimeConverter());

var dateObj = new { Date = new DateTime(2022, 1, 5) };
string json = JsonSerializer.Serialize(dateObj, options); // {"Date":"20220105"}

5. 유용한 팁

역직렬화 시 속성 이름 대소문자 무시 — PropertyNameCaseInsensitive

var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true
};

var json = "{\"name\":\"Vasya\",\"age\":33}";
var person = JsonSerializer.Deserialize<Person>(json, options);
// person.Name == "Vasya"

중첩(깊이)과 최대 객체 수준 제어 — MaxDepth

var options = new JsonSerializerOptions
{
    MaxDepth = 32 // 기본값은 64
};

객체가 무한히 중첩될 수 있는 구조(예: 부모-자식을 가리키는 트리)라면 모델을 다시 설계하는 편이 낫습니다.

JSON의 주석 처리 — ReadCommentHandling (역직렬화)

JSON은 공식적으로 주석을 지원하지 않지만, 가끔 // 같은 주석이 들어간 "창의적인" 파일을 만나기도 합니다.

var options = new JsonSerializerOptions
{
    ReadCommentHandling = JsonCommentHandling.Skip
};

string json = "{\n  \"Name\": \"Ivan\", // 사용자 이름\n  \"Age\": 30\n}";
var person = JsonSerializer.Deserialize<Person>(json, options);

특수 문자 지원 — Encoder

문자열 이스케이프 방식을 제어해야 할 때가 있습니다(예: 키릴 문자를 \uXXXX로 바꾸지 않게). 커스텀 인코더를 지정할 수 있습니다:

using System.Text.Encodings.Web;

var options = new JsonSerializerOptions
{
    Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};

주의해서 쓰세요. 그렇지 않으면 이모지나 한자 같은 문자가 Unicode 시퀀스로 변환될 수 있습니다!

6. 직렬화 설정 시 흔한 실수들

실수 #1: public setter를 깜빡함. setter가 public이 아니거나 필드/속성이 private이면 직렬화되지 않습니다.

실수 #2: null 무시 설정을 잘못 이해함. DefaultIgnoreCondition가 켜져 있으면 null 값은 JSON에 나오지 않습니다 — 기대와 다를 수 있어요.

실수 #3: 날짜 포맷이 다름. 날짜가 원하는 형식으로 직렬화되지 않으면 커스텀 컨버터(JsonConverter)를 추가하세요.

실수 #4: 속성 순서에 의존함. JSON 표준은 필드 순서를 보장하지 않습니다. API가 엄격한 순서를 요구하면 모델을 바꾸거나 외부 라이브러리를 써야 합니다.

실수 #5: 직렬화와 역직렬화에 다른 설정을 사용함. 쓰기와 읽기에 같은 JsonSerializerOptions를 안 쓰면 이름 대소문자나 포맷 관련 오류가 생깁니다.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION