CodeGym /행동 /C# SELF /속성으로 프로세스 제어하기

속성으로 프로세스 제어하기

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

1. 직렬화에서 속성이 왜 필요한가?

객체를 JSON이나 XML로 직렬화할 때, 마치 로봇 청소기가 방을 스캔하는 것처럼 동작합니다. 눈에 보이는 것(공개된 속성/필드)은 전부 가방에 들어가고(파일이나 문자열로), 나머지는 무시됩니다. 그런데 가끔 로봇이 내 가방 속 내용을 몰래 보여주지 않았으면 하거나, 항목 이름을 깔끔하게 바꾸고 싶을 때가 있죠 — 예를 들어 러시아어 대신 영어로.

바로 이때 속성이 등장합니다! 속성으로 직렬화 과정을 제어할 수 있어요: 불필요한 것을 숨기고, 이름을 바꾸고, 순서를 붙이고, 객체의 일부를 무시하게 하거나 시리얼라이저에게 특별한 규칙이 있다고 알려줄 수 있습니다. 마치 물건에 붙이는 스티커 같아요: "건드리지 마세요", "중요", "JSON에서 이름 바꿔".

직렬화 제어의 전형적 과제

  • 출력 문서에서 속성이나 필드의 이름을 변경하기 (예: JSON에서 FirstName 대신 "first_name" 사용)
  • 일부 필드/속성을 직렬화나 역직렬화에서 숨기기 (예: 비밀번호, 내부 카운터)
  • 기본값 처리나 null 값 처리 제어하기
  • 요소 순서 지정하기 (XML에서 특히 중요)
  • XML 요소에 추가 속성(attributes) 붙이기

각 직렬화 플랫폼 — System.Text.Json, Newtonsoft.Json, 또는 XmlSerializer — 은 이러한 목적을 위해 자체 속성 세트를 제공합니다.

2. System.Text.Json용 속성: 최신 스타일

.NET의 기본 JSON 직렬화기는 괜찮은 속성 목록을 제공하며, 이 속성들은 System.Text.Json.Serialization 네임스페이스에 있습니다.

가장 유용한 속성들:

속성 용도 사용 예
JsonPropertyName("...")
JSON에서 속성 이름을 변환
[JsonPropertyName("id")]
JsonIgnore
속성을 직렬화에서 완전히 제외
[JsonIgnore]
JsonInclude
공개 필드(public field)를 직렬화하도록 포함
[JsonInclude]
JsonIgnore(Condition = ...)
특정 조건에서 속성을 무시
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]

사용 예:

using System.Text.Json.Serialization;

public class Person
{
    [JsonPropertyName("first_name")]
    public string FirstName { get; set; } // JSON에서 이름이 'first_name'이 됨

    [JsonIgnore]
    public string Password { get; set; } // JSON에 포함되지 않음

    [JsonPropertyName("born_year")]
    public int? YearOfBirth { get; set; }

    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string Nickname { get; set; } // null이면 JSON에 포함되지 않음
}

이런 사람을 직렬화해봅시다:

var person = new Person
{
    FirstName = "Ivan",
    Password = "123456",
    YearOfBirth = 2000,
    Nickname = null
};

string json = JsonSerializer.Serialize(person);
// json: {"first_name":"Ivan","born_year":2000}

참고: 비밀번호와 별명은 JSON에서 사라졌습니다! (비밀번호는 항상 무시되고, 별명은 null일 때만 제외됩니다).

중요한 이유: 실무에서 클라이언트(또는 악의적 사용자가) 비밀번호, 토큰, 내부 카운터 등 민감한 정보를 보지 못하게 하고 싶을 때가 많습니다. [JsonIgnore] 같은 속성으로 한 줄만에 해결할 수 있습니다.

3. Newtonsoft.Json용 속성: 유연성의 왕

Newtonsoft.Json을 사용하면(문서) 기능이 더 많고, 오래된 프로젝트에 익숙하면 더 편하게 느껴질 수도 있습니다.

주요 속성:

속성 목적
JsonProperty("...")
JSON에서 속성 이름을 지정하고, 순서나 필수 여부 등 설정 가능
JsonIgnore
필드/속성을 직렬화에서 제외
JsonRequired
역직렬화 시 필수 항목으로 요구
JsonConverter
복잡한 경우를 위한 커스텀 컨버터 지정
DefaultValueHandling
기본값 직렬화 처리 제어

실전 예:

using Newtonsoft.Json;

public class UserProfile
{
    [JsonProperty("login")]
    public string Username { get; set; }

    [JsonIgnore]
    public string InternalNotes { get; set; }

    [JsonProperty(Required = Required.Always)]
    public string Email { get; set; }
}

JsonProperty에는 필수성(Required), 순서(Order) 등 추가 옵션이 있습니다. 예:

[JsonProperty("id", Order = 1, Required = Required.Always)]
public int Id { get; set; }

4. XML용 속성: 클래식 스타일

XmlSerializer는 자신의 속성 세트를 사용하며, 이들은 System.Xml.Serialization 네임스페이스에 있습니다.

자주 쓰이는 것들:

속성 용도
[XmlElement("...")]
다른 이름의 XML 요소로 직렬화
[XmlAttribute("...")]
속성을 XML 요소의 attribute로 변환
[XmlIgnore]
XML 직렬화에서 속성이나 필드 제외
[XmlArray("...")]
컬렉션에 대해 XML 배열 이름 지정
[XmlArrayItem("...")]
컬렉션 항목의 XML 요소 이름 지정
[XmlRoot("...")]
루트 XML 태그의 이름 변경

실전 예:

using System.Xml.Serialization;

[XmlRoot("human")]
public class Person
{
    [XmlElement("firstname")]
    public string Name { get; set; }

    [XmlAttribute("years")]
    public int Age { get; set; }

    [XmlIgnore]
    public string Secret { get; set; }
}

var person = new Person { Name = "Anna", Age = 32, Secret = "42" };

직렬화 후의 XML은 대략 이렇습니다:

<human years="32"><firstname>Anna</firstname></human>

Secret 필드는 XML에 포함되지 않았고, Age는 중첩된 태그가 아니라 attribute로 직렬화된 점을 확인하세요.

5. 유용한 뉘앙스

내부 동작: 속성은 어떻게 작동하나

직렬화기가 클래스를 만났을 때, 리플렉션으로(이건 .NET의 마법, 자세한 내용은 System.Reflection 참조) 메타데이터를 읽습니다. 속성으로 JsonIgnoreXmlElement 같은 표시가 있는지 확인하고, 그에 따라 데이터를 결과 문서에 넣거나 건너뜁니다.

이 방식은 "데이터 스키마"를 비즈니스 로직과 분리하는 편리한 수단입니다. 클래스는 비즈니스 로직을 담고, 속성은 직렬화를 위한 여권 같은 역할을 하죠.

주요 속성 대응표

기능 System.Text.Json Newtonsoft.Json XmlSerializer
이름 변경
[JsonPropertyName]
[JsonProperty]
[XmlElement]
[XmlAttribute]
무시하기
[JsonIgnore]
[JsonIgnore]
[XmlIgnore]
커스텀 포맷
[JsonConverter]
[JsonConverter]
— (IXmlSerializable로 처리, 번거로움)
루트 객체
[XmlRoot]
컬렉션
[XmlArray]
[XmlArrayItem]

6. 고급 시나리오

표준 직렬화 방식으로는 부족할 때가 있습니다. 예를 들어 날짜를 ISO 문자열이 아니라 UNIX timestamp로 저장하고 싶다면, 커스텀 컨버터를 연결해야 합니다.

System.Text.Json에서:

public class UnixDateTimeConverter : JsonConverter<DateTime>
{
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => DateTimeOffset.FromUnixTimeSeconds(reader.GetInt64()).UtcDateTime;

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
        => writer.WriteNumberValue(new DateTimeOffset(value).ToUnixTimeSeconds());
}

public class LogEntry
{
    [JsonConverter(typeof(UnixDateTimeConverter))]
    public DateTime EventTime { get; set; }
}

이제 직렬화할 때 EventTime은 문자열이 아니라 숫자로 표현됩니다.

Newtonsoft.Json에서:

public class BoolToYesNoConverter : JsonConverter<bool>
{
    public override void WriteJson(JsonWriter writer, bool value, JsonSerializer serializer)
        => writer.WriteValue(value ? "yes" : "no");

    public override bool ReadJson(JsonReader reader, Type objectType, bool existingValue, bool hasExistingValue, JsonSerializer serializer)
        => (string)reader.Value == "yes";
}

public class Answer
{
    [JsonConverter(typeof(BoolToYesNoConverter))]
    public bool IsCorrect { get; set; }
}

이제 불리언 값은 "yes" 또는 "no"로 직렬화되고, 역직렬화 시 true 또는 false로 복원됩니다.

7. 특징과 함정

속성을 추가할 때 기억하세요: 서로 다른 직렬화기는 서로 다른 속성을 사용합니다. JSON과 XML을 동시에 다루거나(심지어 Newtonsoft.JsonSystem.Text.Json을 동시에 쓰는 경우에도) 필요한 속성을 둘 다 달아주지 않으면 깜짝 놀랄 결과가 생깁니다.

속성 이름은 속성으로 오버라이드하지 않으면 직렬화기가 기본 이름을 사용합니다.

상속을 조심하세요: 자식 클래스는 부모의 public 필드/속성을 상속하고, 부모에 붙은 속성도 자식에 적용됩니다. 이 점은 종종 놀라움을 주지만 의도된 동작입니다.

흔한 실수: 개발자가 실수로 직렬화되어야 할 필드에 JsonIgnore를 붙이거나(또는 반대로 민감한 데이터를 무시하지 않는 경우), 예를 들어 XmlElement를 private 필드에 붙이고 왜 직렬라이저가 보지 못하냐고 당황하는 경우가 많습니다. (XmlSerializerpublic 멤버만 처리합니다!)

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