CodeGym /행동 /C# SELF /XML 읽기/쓰기: XmlSerializer

XML 읽기/쓰기: XmlSerializer

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

1. 소개

XML (eXtensible Markup Language)은 확장 가능한 마크업 언어로, 복잡하게 구조화된 데이터를 저장하고 교환할 때 많이 쓰이는 포맷이야. JSON에 비해 간결하지는 않지만 은행권, 정부 서비스 간 교환, 문서, 1C/SAP/Oracle 연동 등에서 여전히 활발히 사용돼. Windows와 .NET의 일부 데이터(예: 설정 파일)도 여전히 XML로 보관되는 경우가 많아.

외형상 XML은 HTML을 닮았지만 <body>나 <div>처럼 고정된 태그 집합이 없고, 태그 구조를 직접 정의해서 사용해. 예:

<Person>
  <FirstName>이반</FirstName>
  <LastName>페트로프</LastName>
  <Age>30</Age>
</Person>

XML 기초와 C# 객체 매핑

JSON과 달리 XML에는 배열이나 불(bool) 같은 전용 타입 표현이 있는 게 아니라 모든 게 엘리먼트와 어트리뷰트로 표현돼. 리스트 구조는 같은 이름의 반복되는 엘리먼트로 구현하는 식이야.

XML을 C#에서 편하게 다루려면 XmlSerializer를 이용한 직렬화/역직렬화 메커니즘을 사용해.

직렬화 — C# 객체를 XML 문자열로 바꾸는 것. 역직렬화 — 반대로 XML에서 C# 객체를 복원하는 것.

클래스가 몇 가지 요구사항을 만족하면 이건 별다른 설정 없이 바로 동작해 — 아래에서 자세히 볼 거야.

XmlSerializer를 쓸까?

XmlSerializer는 .NET 표준 도구야 (네임스페이스 System.Xml.Serialization) — 객체를 XML로, XML을 객체로 바꾸는 데 간단하고 타입 안전하며 데이터 임포트/엑스포트, 설정 파일, 시스템 간 교환이나 정부 서비스 연동에 적합해.

2. 예제: 객체 직렬화

사용자 모델이 있다고 하자:

public class User
{
    public string FirstName { get; set; }
    public string LastName  { get; set; }
    public int Age          { get; set; }
}

객체를 XML로 직렬화해보자:

using System.Xml.Serialization;

User user = new User { FirstName = "이반", LastName = "페트로프", Age = 30 };
// 직렬화하려면 타입이 필요해
XmlSerializer serializer = new XmlSerializer(typeof(User));

// XML을 파일에 씀
using FileStream fs = new FileStream("user.xml", FileMode.Create);
serializer.Serialize(fs, user);

무슨 일이 일어날까?

- User 클래스의 객체를 만든다.
- 해당 타입(User)에 대해 XmlSerializer 인스턴스를 생성한다.
- FileStream으로 파일을 연다.
- Serialize()를 호출하면 객체를 XML로 변환해서 파일에 기록한다.

user.xml 파일 내용:

<?xml version="1.0"?>
<User xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <FirstName>이반</FirstName>
  <LastName>페트로프</LastName>
  <Age>30</Age>
</User>

정말 간단해 — 최소한의 코드로 바로 XML을 만들 수 있어.

3. 역직렬화: XML을 다시 객체로 읽기

생성한 XML을 읽어서 C# 객체로 복원해보자:

// XML 파일을 연다
using FileStream fs = new FileStream("user.xml", FileMode.Open);
User? loadedUser = serializer.Deserialize(fs) as User;
Console.WriteLine($"{loadedUser?.FirstName} {loadedUser?.LastName}, 나이: {loadedUser?.Age}");

Deserialize() 메서드는 object를 반환하므로 User로 캐스팅(여기서는 as)해야 해. 파일이 모델에 맞으면 객체가 정상적으로 복원돼.

4. 직렬화 동작 방식: 일반적인 특징

XML 직렬화는 몇 가지 규칙을 따르는걸 요구해:

• 직렬화하려는 클래스는 파라미터 없는 public 기본 생성자를 가져야 해.

• 직렬화되는 프로퍼티는 public이어야 하고 getter와 setter가 모두 있어야 해. private 멤버는 무시돼.

• 읽기 전용 프로퍼티, static 또는 abstract 프로퍼티는 동작하지 않아.

규칙을 어기면 InvalidOperationException이 발생하고 메시지로 "XmlSerializer cannot serialize this type" 같은 걸 볼 수 있어.

5. 컬렉션 직렬화: 배열과 리스트

여러 사용자를 직렬화하려면 래핑 클래스를 쓰는 게 일반적이야:

public class UserList
{
    public List<User> Users { get; set; } = new List<User>();
}

컬렉션을 만들고 직렬화해보자:

UserList users = new UserList
{
    Users = new List<User>
    {
        new User { FirstName = "이반", LastName = "페트로프", Age = 30 },
        new User { FirstName = "마리아", LastName = "이바노바", Age = 25 }
    }
};

XmlSerializer serializer = new XmlSerializer(typeof(UserList));
using FileStream fs = new FileStream("users.xml", FileMode.Create);
serializer.Serialize(fs, users);

생성되는 XML:

<?xml version="1.0"?>
<UserList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Users>
    <User>
      <FirstName>이반</FirstName>
      <LastName>페트로프</LastName>
      <Age>30</Age>
    </User>
    <User>
      <FirstName>마리아</FirstName>
      <LastName>이바노바</LastName>
      <Age>25</Age>
    </User>
  </Users>
</UserList>

List<User> 자체를 바로 직렬화할 수도 있지만, 그 경우 루트 엘리먼트가 ArrayOfUser처럼 자동으로 생성되니 통합 요구사항에 맞지 않을 수 있어.

6. 직렬화 포맷 제어를 위한 어트리뷰트

어트리뷰트를 사용하면 XML 포맷을 제어할 수 있어: 엘리먼트 이름 변경, 어트리뷰트로 직렬화, 필드 무시 등. 자주 쓰는 어트리뷰트: [XmlElement], [XmlAttribute], [XmlArray], [XmlIgnore].

어트리뷰트로 직렬화하기

이름과 성은 어트리뷰트로, 나이는 엘리먼트로 기록해볼게:

public class User
{
    [XmlAttribute]
    public string FirstName { get; set; }

    [XmlAttribute]
    public string LastName { get; set; }

    [XmlElement]
    public int Age { get; set; }
}

결과:

<User FirstName="이반" LastName="페트로프">
  <Age>30</Age>
</User>

엘리먼트 이름 제어

태그 이름을 좀 더 사람 친화적으로 바꿔보자:

public class User
{
    [XmlElement("이름")]
    public string FirstName { get; set; }

    [XmlElement("성")]
    public string LastName { get; set; }

    [XmlElement("나이")]
    public int Age { get; set; }
}

생성되는 XML:

<User>
  <이름>이반</이름>
  <성>페트로프</성>
  <나이>30</나이>
</User>

속성 무시하기

어떤 속성을 직렬화하고 싶지 않다면 [XmlIgnore]로 표시하면 돼:

public class User
{
    public string FirstName { get; set; }

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

7. 계층 구조와 중첩 객체 직렬화

중첩된 클래스는 그대로 중첩된 XML 엘리먼트로 변환돼:

public class Address
{
    public string City { get; set; }
    public string Street { get; set; }
}

public class User
{
    public string FirstName { get; set; }
    public Address Address { get; set; }
}
var user = new User
{
    FirstName = "안나",
    Address = new Address { City = "네옹그라드", Street = "비야조바야" }
};

XmlSerializer serializer = new XmlSerializer(typeof(User));
using var fs = new FileStream("user_with_address.xml", FileMode.Create);
serializer.Serialize(fs, user);

생성되는 XML:

<User>
  <FirstName>안나</FirstName>
  <Address>
    <City>네옹그라드</City>
    <Street>비야조바야</Street>
  </Address>
</User>

8. 문자열로 직렬화/역직렬화 (파일 없이)

문자열로 직렬화

using System.Text;

var serializer = new XmlSerializer(typeof(User));
using var stringWriter = new StringWriter();

serializer.Serialize(stringWriter, user);
string xml = stringWriter.ToString();
Console.WriteLine(xml);

문자열에서 역직렬화

var xml = "<User><FirstName>안나</FirstName></User>";
using var stringReader = new StringReader(xml);
User user = (User)serializer.Deserialize(stringReader);
Console.WriteLine(user.FirstName);

9. 예제: 애플리케이션 데이터 내보내기/가져오기

사용자 리스트를 XML 파일로 내보내고 가져오는 예:

public static void ExportUsers(UserList users, string path)
{
    var serializer = new XmlSerializer(typeof(UserList));
    using var fs = new FileStream(path, FileMode.Create);
    serializer.Serialize(fs, users);
}

public static UserList ImportUsers(string path)
{
    var serializer = new XmlSerializer(typeof(UserList));
    using var fs = new FileStream(path, FileMode.Open);
    return (UserList)serializer.Deserialize(fs);
}

용도: 백업, 다른 서비스와의 데이터 교환, 통합 작업 등. 실무에서 XmlSerializer 관련 질문은 생각보다 자주 나오는데, 특히 공공 서비스나 은행 관련 시스템을 다루는 회사에서는 더 그렇다.

10. 흔한 오류와 동작상의 특징

필드가 public이 아니면 직렬화되지 않아.

읽기 전용 프로퍼티는 무시돼.

인터페이스 타입이나 추상 클래스 타입의 프로퍼티는 XmlSerializer가 지원하지 않아.

타입 DateTime은 XSD 포맷으로 직렬화돼(참고: 문서).

배열이나 List<T> 같은 컬렉션은 예측 가능하게 동작하지만 ObservableCollection<T>는 외부 시스템에서 예상치 못한 결과를 낼 수 있어.

인코딩 주의사항: StreamWriter를 사용할 때 인코딩을 명시하지 않으면 결과가 UTF-16으로 나올 수 있고, 호환성상 대개는 UTF-8을 기대하니까 인코딩을 명시해주는 게 좋아.

2
과제
C# SELF, 레벨 48, 레슨 0
잠금
객체 리스트 직렬화
객체 리스트 직렬화
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION