Newtonsoft.Json은 .NET 생태계에서 가장 널리 쓰이는 JSON 처리 라이브러리로, 빠른 성능과 풍부한 기능을 제공한다.
Unity에는 기본 JSON 직렬화 도구인 JsonUtillity가 있지만, 딕셔너리나 다형성 등 지원하지 않는 기능에 제한이 많다.
예를 들어, JsonUtility는 Vector3 같은 일부 Unity 타입을 처리하지만 배열 루트는 직접 지원하지 않고, Dictionary는 아예 처리하지 못한다.
Newtonsoft.Json은 모든 공개 속성과 다양한 컬렉션, 사용자 지정 타입, 복잡한 객체 구조도 직렬화/역직렬화할 수 있어 유용하다.
다만 Unity의 JsonUtility가 단순 직렬화에서는 더 빠를 수 있지만, 기능 면에서 풍부함이 Newtonsoft.Json의 장점이다.
Newtonsoft.Json를 잘 사용하기 위해서, 설치부터 사용 방법, 활용까지 한 번 정리해본다.
com.unity.nuget.newtonsoft-json 입력 후 AddNewtonsoft.Json은 직렬화/역질렬화를 위한 도구이고, 이를 위한 기능은 JsonConvert 클래스를 통해 수행된다.
직렬화는 SerializeObject, 역직렬화는 DeserializeObject를 사용한다.
이에 대해서 자세히 살펴보자.
PlayerData data = new PlayerData { name = "Hero", level = 5 };
string json = JsonConvert.SerializeObject(data);
위 코드를 통해서 data 객체가 JSON 문자열로 변환된다.
반대로 역직렬화는 다음과 같다.
string json = "{\"name\":\"Hero\",\"level\":5}";
PlayerData loaded = JsonConvert.DeserializeObject<PlayerData>(json);
이렇게 하면 PlayerData 클래스 인스턴스가 생성되어 name과 level 값이 채워진다.
Serialize 와 Deserialize는 서로 대응하며, 제네릭 <T> 타입을 정확하게 지정해야 한다.
Unity 환경에서 리스트를 직렬화/역직렬화는 다음과 같이 구현한다.
List<Card> card = new List<Card> {/* .... */};
//객체 -> JSON 문자열
string serialized = JsonConvert.SerializeObject(card);
//JSON -> 객체
List<Card> deserialized = JsonConvert.DeserializeObject<List<Card>>(serialized);
JsonConvert 메서드는 JsonSerializerSettings를 통해 직렬화 방식을 세밀하게 조정할 수 있다. 자주 사용하는 옵션은 다음과 같다.
Formatting.Indented로 설정하면 출력 JSON이 들여쓰기된 형태(보기 편한 포맷)로 나온다. 기본값(None)은 한 줄짜리 compact JSON입니다.NullValueHandling.Ignore로 설정하면 값이 null인 속성은 JSON에 포함되지 않는다. var settings = new JsonSerializerSettings {
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
};
string json = JsonConvert.SerializeObject(person, settings); 이렇게 하면 null인 필드는 JSON 출력에서 빠진다.Error 로 순환을 발견하면 예외를 던지지만 이를 무시하면 ReferenceLoopHanding.Ignore 를 사용한다.var settings = new JsonSerializerSettings {
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
string json = JsonConvert.SerializeObject(objWithLoops, settings); Ignore로 설정하면 순환 참조를 역직렬화 과정에서 건너 뛴다.PreserveReferencesHandling.Objects 로 설정하면 된다.표로 정리하면 다음과 같다.
기본 직렬화 동작 조절 관련
| 속성 | 설명 | 예시 |
|---|---|---|
Formatting | JSON 문자열 포맷 지정 (None, Indented) | Formatting.Indented 사용 시 보기 좋게 들여쓰기 |
NullValueHandling | null 값을 무시하거나 포함할지 설정 | NullValueHandling.Ignore |
DefaultValueHandling | 기본값 포함 여부 설정 | DefaultValueHandling.Ignore |
MissingMemberHandling | 존재하지 않는 멤버를 역직렬화할 때 오류 처리 | MissingMemberHandling.Error |
ObjectCreationHandling | 기존 객체를 재사용하거나 새로 생성할지 | ObjectCreationHandling.Replace |
보안/ 타입 관련
| 속성 | 설명 | 예시 |
|---|---|---|
TypeNameHandling | 형식 이름을 JSON에 포함시켜 다형성 지원 (None, Objects, Auto, All) | TypeNameHandling.Auto 주의: 보안상 위험할 수 있으니 신중하게 사용해야 해 |
TypeNameAssemblyFormatHandling | 형식 이름의 어셈블리 포맷 지정 | TypeNameAssemblyFormatHandling.Simple |
날짜 및 시간 처리
| 속성 | 설명 | 예시 |
|---|---|---|
DateFormatHandling | 날짜 포맷 처리 방식 (예: ISO vs Microsoft) | DateFormatHandling.IsoDateFormat |
DateTimeZoneHandling | 시간대 처리 방식 | DateTimeZoneHandling.Utc |
DateFormatString | 사용자 지정 날짜 포맷 문자열 | "yyyy-MM-dd HH:mm:ss" |
순환참조 및 깊이 제한
| 속성 | 설명 | 예시 |
|---|---|---|
ReferenceLoopHandling | 순환 참조가 있을 때 처리 방식 | ReferenceLoopHandling.Ignore |
PreserveReferencesHandling | 참조를 유지할지 여부 | PreserveReferencesHandling.Objects |
MaxDepth | 역직렬화 최대 깊이 제한 | MaxDepth = 10 |
사용자 정의 핸들링
| 속성 | 설명 | 예시 |
|---|---|---|
ContractResolver | 어떤 속성을 직렬화할지 결정하는 로직 | new CamelCasePropertyNamesContractResolver() (낙타표기법) |
Converters | 사용자 정의 컨버터 등록 | Converters.Add(new Vector3Converter()) |
Error | 직렬화 오류 발생 시 이벤트 핸들링 | settings.Error += (sender, args) => { ... }; |
Newtonsoft.Json은 C# 속성에 특별한 어트리뷰트를 달아 직렬화/역직렬화 동작을 제어할 수 있다. 자주 사용한 것을 자세히 정리해보고, 그 외에는 표로 간단히 정리해두었다.
public class Account
{
public string FullName { get; set; }
public string Email { get; set; }
[JsonIgnore]
public string Password { get; set; }
} 이 경우 Parssword 값은 직렬화 결과에 나타나지 않는다.Name 이라도 JSON 에서 name으로 쓰려면 public class Game
{
[JsonProperty("name")]
public string Name { get; set } 이렇게 하면 JSON 출력 시 속성명이 지정한 값으로 나타난다.[JsonConverter]를 사용하면 해당 클래스나 속성에 특별한 직렬화/역직렬화 로직을 연결할 수 있다.표로 정리하면 다음과 같다.
| 어트리뷰트 | 대상 | 설명 |
|---|---|---|
[JsonProperty] | 속성, 필드 | JSON 속성 이름 지정, 순서, 기본값, 필수 여부 등 설정 가능 |
[JsonIgnore] | 속성, 필드 | JSON에 직렬화/역직렬화 제외 |
[JsonConverter] | 속성, 클래스 | 특정 필드나 타입에 커스텀 컨버터 적용 |
[JsonObject] | 클래스 | 객체 직렬화 방식 지정 (MemberSerialization.OptIn, OptOut) |
[JsonConstructor] | 생성자 | 역직렬화 시 호출될 생성자 지정 |
[JsonExtensionData] | Dictionary<string, JToken> | 정의되지 않은 여분의 데이터를 자동 수집 |
[JsonRequired] | 속성 | 역직렬화 시 반드시 있어야 하는 필드 (없으면 예외 발생) |
[JsonIgnoreCondition] (.NET 5+) | 속성 | WhenWritingNull, WhenWritingDefault 등 조건 기반 무시 (System.Text.Json 전용이지만 유사 개념으로 이해 가능) |
지금까지 기본적인 Newtonsoft.Json의 직렬화/역직렬화 도구를 살펴 보았다.
이제 이를 바탕으로 복잡한 타입을 처리할 수 있는 사용자 정의 컨버터를 만들어보자.
Unity의 Vector3 구조체는 기본 직렬화가 순환 참조를 일으켜 일반 설정으로는 직렬화 되지 않는다.
이를 위해 JsonConverter를 상속해 클래스를 구현할 수 있다. 자세한 설명은 주석으로 정리해두었다.
// Newtonsoft.Json에서 사용자 정의 변환기를 만들기 위해 JsonConverter를 상속함
public class Vector3Converter : JsonConverter
{
// Json.NET이 이 컨버터를 언제 사용할지 판단하는 메서드
public override bool CanConvert(Type objectType)
{
// 이 컨버터는 Vector3 타입에만 동작하도록 지정
return objectType == typeof(Vector3);
}
// 객체를 JSON으로 변환할 때 호출됨
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// value를 Vector3 타입으로 캐스팅
Vector3 v = (Vector3)value;
// JSON 객체 시작 {
writer.WriteStartObject();
// "x" : v.x
writer.WritePropertyName("x");
writer.WriteValue(v.x);
// "y" : v.y
writer.WritePropertyName("y");
writer.WriteValue(v.y);
// "z" : v.z
writer.WritePropertyName("z");
writer.WriteValue(v.z);
// JSON 객체 종료 }
writer.WriteEndObject();
}
// JSON을 읽어 C# 객체(Vector3)로 역직렬화할 때 호출됨
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// JObject로 파싱해서 JSON 객체를 메모리로 로드
JObject obj = JObject.Load(reader);
// "x", "y", "z" 키의 값을 float으로 추출
float x = (float)obj["x"];
float y = (float)obj["y"];
float z = (float)obj["z"];
// 새로운 Vector3(x, y, z) 생성 후 반환
return new Vector3(x, y, z);
}
}
이 컨버터를 사용하려면 컨버터를 등록하거나 [JsonConverter] 어트리뷰트를 사용하면 된다.
var settings = new JsonSerializerSettings();
settings.Converters.Add(new Vector3Converter()); // JsonSerializerSettings에 등록
//------------------------------------------------
public class PlayerPosition {
[JsonConverter(typeof(Vector3Converter))] //[JsonConverter] 어트리뷰트
public Vector3 position;
}
Newtonsoft.Json의 성능 및 메모리 최적화 전략에 대해서 한 번 짚고 넘어가보자.
아주 큰 JSON을 다룰 때는 문자열 전체를 메모리에 올리지 않고 스트림으로 읽어들여
GC 압박을 줄이는 전략이다.
using (Stream s = File.OpenRead(filePath))
using (StreamReader sr = new StreamReader(s))
using (JsonTextReader reader = new JsonTextReader(sr)) {
JsonSerializer serializer = new JsonSerializer();
var result = serializer.Deserialize<MyType>(reader);
}
위 전략은 85KB 이상의 큰 JSON을 처리를 할 때 사용한다. 그 이하는 크게 의미없다.
위에서 using은 네임스페이스 import가 아니라
tream, StreamReader, JsonTextReader는 파일 핸들, 메모리 버퍼, 내부 리소스 등을 갖고 있는 IDisposable 객체의 자원을 안전하게 해제하기 위해서 사용하였다.
코드로 풀어보면 다음과 같은 역할을 수행한다.
Stream s = File.OpenRead(filePath);
try
{
StreamReader sr = new StreamReader(s);
try
{
JsonTextReader reader = new JsonTextReader(sr);
try
{
// JSON 처리
}
finally { reader.Dispose(); }
}
finally { sr.Dispose(); }
}
finally { s.Dispose(); }
가능한 경우 JsonTextWriter를 직접 사용해 수동 직렬화를 구현하면 가장 빠르다.
사용자 지정 JsonConverter 사용 시 CanConvert 체크 오버헤드가 있다.
성능이 중요한 경우에는 [JsonConverter] 어트리뷰트를 사용해 특정 타입에 한정 적용하거나, 맞춤형 IContractResolver를 만들어 컨버터를 바인딩하는 방법도 고려할 만 하다.
JsonUtility와 달리 Newtonsoft.Json은 BOM(Byte Order Mark)이 포함될 수 있으므로,JsonIgnore를 활용해 보안상 노출해서는 안 되는 내부 정보(예: 비밀번호 해시, 내부 식별자 등)가 JSON으로 노출되지 않도록 하는 것이 바람직하다.