CodeGym /행동 /C# SELF /XML 직렬화 설정

XML 직렬화 설정

C# SELF
레벨 48 , 레슨 1
사용 가능

1. 소개

이미 알다시피 .NET은 기본적으로 공개 필드와 속성을 기반으로 객체를 XML로 직렬화/역직렬화할 수 있어. 그런데 예쁘고 남의 프로그램이나 표준과 호환되는 XML을 만들고 싶거나, XML에 포함하고 싶지 않은 내부 정보를 제외하고 싶을 때는 좀 더 세밀한 설정이 필요하지.

C#에는 이를 위한 직렬화 어트리뷰트가 있어 — 클래스나 속성, 필드에 붙이는 특수한 '태그'야. 이 어트리뷰트들 덕분에 어떤 필드가 XML에 들어갈지, 어떤 건 무시할지, 요소 이름을 뭐로 할지, 속성으로 만들지, 복잡한 구조를 가질지, 심지어 요소들의 순서까지 완전히 제어할 수 있어!

기본 직렬화는 자동으로 양식 채우는 것과 같아: 이름-성-부모 이름이 각 칸에 자동으로 들어가고 나머진 알아서 하는 수준. 근데 어트리뷰트를 쓰면 너는 마치 슈퍼 공무원처럼 각 줄을 어디에 쓸지, 뭐는 건너뛸지, 뭐는 빨갛게 표시할지, 뭐는 비밀 구역에 숨길지 전부 결정할 수 있어.

객체와 XML의 매핑

클래스/속성 어트리뷰트 XML 결과
[XmlRoot("Persons")]
클래스 UserList
<Persons>...</Persons>
List<User> Users
[XmlElement("Person")]
<Person>...</Person>
int Id
[XmlAttribute("id")]
id="..." (어트리뷰트)
string Name
[XmlElement("FullName")]
<FullName>...</FullName>
string[] Emails
[XmlArray("EMails")][XmlArrayItem("Email")]
<EMails><Email>...</Email></EMails>
DateTime RegisteredAt
[XmlIgnore] (RegisteredAtString 참조) -
string RegisteredAtString
[XmlAttribute("registered")]
registered="..."
Address[] Addresses
[XmlArray("UserAddresses")][XmlArrayItem("Address")]
<UserAddresses>...</UserAddresses>

2. 주요 XML 직렬화 어트리뷰트 개요

가장 많이 쓰이는 어트리뷰트들을 쭉 훑어볼게 — .NET 개발자들이 오랜 기간 클래스에 붙여온 것들이야!

어트리뷰트 [XmlElement] — 요소 이름

XML에서 요소 이름을 바꾸거나 속성/필드를 XML 요소로 표시할 때 써.

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

XML:

<Person>
  <FullName>바실리 페트로프</FullName>
</Person>

어트리뷰트를 안 쓰면 기본적으로 요소 이름은 속성/필드 이름과 같아.

어트리뷰트 [XmlAttribute] — XML 속성으로 직렬화

속성/필드를 XML 요소가 아니라 XML 속성으로 직렬화하고 싶을 때 사용해.

public class Person
{
    [XmlAttribute("id")]
    public int Id { get; set; }
}

XML:

<Person id="123"></Person>

실제 XML API에서는 식별자, 날짜, 플래그 같은 데에 속성을 자주 쓰지.

어트리뷰트 [XmlIgnore] — 필드/속성 건너뛰기

완전한 파라노이아용: 직렬화/역직렬화 과정에서 특정 속성을 완전히 제외하고 싶을 때! 이 어트리뷰트가 붙은 건 최종 XML에 전혀 들어가지 않아.

public class Person
{
    [XmlIgnore]
    public string InternalNote { get; set; }
}

XML: (InternalNote 속성은 없음)

어트리뷰트들 [XmlArray][XmlArrayItem] — 컬렉션 제어

배열과 컬렉션 전용 어트리뷰트야. [XmlArray]는 바깥 래퍼 배열 태그 이름을, [XmlArrayItem]은 각 아이템의 태그 이름을 지정해.

public class Person
{
    [XmlArray("Phones")]
    [XmlArrayItem("Phone")]
    public string[] PhoneNumbers { get; set; }
}

XML:

<Person>
  <Phones>
    <Phone>+12951234567</Phone>
    <Phone>+12876543210</Phone>
  </Phones>
</Person>

어트리뷰트 [XmlRoot] — 루트 요소 이름

클래스 자체에 붙여서 XML의 루트 요소 이름을 바꿀 수 있어.

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

XML:

<User>
  <Name>안나</Name>
</User>

어트리뷰트 [XmlText] — 요소의 텍스트 내용으로 직렬화

때로는 속성 값이 요소 안의 단순 텍스트로 들어가야 할 때가 있어 — 하위 요소나 속성이 아니라 요소의 텍스트 내용으로.

public class Note
{
    [XmlText]
    public string Content { get; set; }
}

XML:

<Note>할머니에게 전화해!</Note>

어트리뷰트들 [XmlNamespaceDeclarations], [XmlElement(Type = ...)]

네임스페이스를 다루거나 상속을 지원하는 등 좀 더 고급 시나리오를 위해서는 더 세부적인 어트리뷰트들이 있어. 전체 목록은 공식 XML 직렬화 문서를 참고해.

3. 실습: 실제 예제로 직렬화 개선하기

한 단계씩 우리 학습용 앱을 'XML 직렬화 프리미엄'으로 다듬어 보자 — 엄격한 XML 검사자도 만족하게 만들 거야.

어트리뷰트 없는 기본 클래스

예를 들어 이런 사용자 클래스가 있다고 해보자:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string[] Emails { get; set; }
    public DateTime RegisteredAt { get; set; }
    public string Password { get; set; }
}

기본 직렬화는 대략 이런 XML을 만들어 (날짜는 자체 포맷으로 들어감):

<User>
  <Id>42</Id>
  <Name>Ivan Ivanov</Name>
  <Emails>
    <string>user@mail.com</string>
    <string>admin@site.org</string>
  </Emails>
  <RegisteredAt>2024-07-01T00:00:00</RegisteredAt>
  <Password>qwerty</Password>
</User>

문제점:

  • 비밀번호는 차라리 직렬화하지 않는 게 안전해.
  • 날짜는 속성으로 사람 친화적인 포맷을 원해.
  • Emails를 더 예쁘게 표현하고 싶어.
  • 루트 요소를 <Person>로 바꾸고 싶어.

어트리뷰트로 설정 추가하기

[XmlRoot("Person")]
public class User
{
    [XmlAttribute("id")]
    public int Id { get; set; }

    [XmlElement("FullName")]
    public string Name { get; set; }

    [XmlArray("EMails")]
    [XmlArrayItem("Email")]
    public string[] Emails { get; set; }

    [XmlAttribute("registered")]
    public string RegisteredAtString
    {
        get => RegisteredAt.ToString("yyyy-MM-dd");
        set => RegisteredAt = DateTime.Parse(value);
    }

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

    [XmlIgnore]
    public DateTime RegisteredAt { get; set; }
}

세부 설명:

  • 날짜를 속성으로 만들기 위해 [XmlAttribute("registered")]를 사용했어.
  • DateTime 자체를 직렬화하는 대신 문자열 표현을 직렬화하려고, 별도의 속성 RegisteredAtString을 추가하고 실제 필드는 [XmlIgnore]로 숨겼어.
  • 비밀번호는 [XmlIgnore]로 숨김.
  • 이메일 배열은 바깥 태그를 <EMails>로, 각 이메일을 <Email>로 지정했어.

결과:

<Person id="42" registered="2024-07-01">
  <FullName>Ivan Ivanov</FullName>
  <EMails>
    <Email>user@mail.com</Email>
    <Email>admin@site.org</Email>
  </EMails>
</Person>

4. 중첩 객체 다루기

사용자와 주소 같은 중첩 객체가 있으면 XML 표현이 더 진가를 발휘해.

public class Address
{
    [XmlAttribute]
    public string City { get; set; }

    [XmlText]
    public string Details { get; set; }
}

public class User
{
    // ...다른 속성들, 위를 참고...
    public Address[] Addresses { get; set; }
}

주소 배열에 대한 설정을 추가해보자:

[XmlArray("UserAddresses")]
[XmlArrayItem("Address")]
public Address[] Addresses { get; set; }

그럼 XML은 이렇게 될 거야:

<Person id="42" registered="2024-07-01">
  <FullName>Ivan Ivanov</FullName>
  <EMails>
    <Email>user@mail.com</Email>
  </EMails>
  <UserAddresses>
    <Address City="Neon city">잠코바야 거리, 1번지</Address>
    <Address City="North Cave">크라이냐야 거리, 12번지</Address>
  </UserAddresses>
</Person>

<Address>에서 city는 어트리뷰트로 들어가고, 주소 본문은 요소의 텍스트로 들어간다는 걸 볼 수 있어.

5. 자주 하는 실수와 주의점

복잡한 객체를 직렬화했는데 기대한 대로 XML이 안 바뀌었다면 — 어트리뷰트를 제대로 붙이는 걸 깜빡했을 가능성이 높아. 어떤 속성들이 마킹되지 않으면 .NET은 그냥 기본 동작을 선택해.

또 한 가지 함정은 컬렉션 이름 문제야. 예를 들어 문자열 배열에 [XmlArray][XmlArrayItem]를 안 붙이면 요소들이 string 태그로 기록될 수 있어 (위의 초기 예 참고). 원하지 않으면 항상 명시적으로 태그 이름을 지정해줘.

getter-only(읽기 전용) 속성은 기본적으로 역직렬화되지 않으니 주의해. 가능하면 public set을 만들어 줘.

사전(Dictionary)이나 인터페이스 같은 일부 타입은 기본 XmlSerializer로 직렬화되지 않거나 별도 처리가 필요해.

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