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를 안 쓰면 이름 대소문자나 포맷 관련 오류가 생깁니다.
GO TO FULL VERSION