MessagePack은 JSON의 바이너리 프로토콜입니다. 구글의 ProtoBuf 와 함께 대중적으로 사용되며 특히 Unity에서 게임 데이터를 직렬화 하는데 대중적으로 쓰입니다. 대표적인 MessagePack 라이브러리로는 neuecc/MessagePack-CSharp 입니다.
MessagePack-CSahrp에서 제공하는 퍼포먼스 비교표를 보면 protobuf-net
보다 5배 이상 빠름을 알 수 있습니다. 게임에서 적극적으로 사용하는 직렬화 라이브러리인 만큼 상용 프로그램에 적용하는데 부족함이 없습니다.
본 시간에는 MessagePack-CSharp
를 설치하고 사용법을 간략히 숙지해서 라이브러리를 쓸 수 있도록 진행해봅시다.
MessagePack-Chsarp 설치
NuGet에서 MessagePack
및 MessagePackAnalyzer
를 설치합니다.
설치하는 패키 중 MessagePackAnalyzer
은 MessagePack
관련 속성이 제대로 부여되지 않았을 경우 컴파일 오류가 발생하도록 Analyzer 기능이 추가됩니다.
간단히 코딩 해보기
여러분이 바로 프로젝트에 MessagePack을 활용해볼 수 있게 MessagePack의 기본 동작을 간단히 확인해보겠습니다. (코드는 .NET 6 Preview 7에서 테스트 하였습니다.)
using MessagePack;
// 더미 정보 생성
var user1 = new UserInfo
{
Id = "user1",
Name = "사용자1",
DummyValue = 312
};
// 직렬화. 반환값은 바이트 배열
var binUser1 = MessagePackSerializer.Serialize(user1);
// 직렬화된 데이터를 JSON으로 확인할 수 있음
var jsonUser1 = MessagePackSerializer.ConvertToJson(binUser1);
Console.WriteLine(jsonUser1);
// 복호화 한 다음 출력 (feat. 클래스 대신 레코드를 이용하면 출력이 이쁘게 나옴)
var newUser1 = MessagePackSerializer.Deserialize<UserInfo>(binUser1);
Console.WriteLine(newUser1);
[MessagePackObject]
public record UserInfo
{
[Key(0)]
public string? Id { get; set; }
[Key(1)]
public string? Name { get; set; }
[IgnoreMember]
public int DummyValue { get; set; }
}
- 직렬화 대상은
[MessagePackObject]
Attribute를 적용해야 합니다. 그리고 대상은 반드시public
이어야 합니다. - 직렬화 속성은 키를 부여해야 합니다. 자동으로 키를 부여하는 옵션도 있지만
속성명
으로 부여되므로 추천하지 않습니다. 키의 인덱스는 직렬화된 정보의 위치가 됩니다(JSON 출력 결과 참조) - 속성 중 직렬화 대상이 아니어야 하는 것은
[IgnoreMember]
Attribute를 부여합니다.
출력
["user1","사용자1"]
UserInfo { Id = user1, Name = 사용자1, DummyValue = 0 }
상속 및 사용자유형 코딩하기
var binUser2 = MessagePackSerializer.Serialize(detailUser);
var jsonUser2 = MessagePackSerializer.ConvertToJson(binUser2);
Console.WriteLine(jsonUser2);
var newUser2 = MessagePackSerializer.Deserialize<UserDetailInfo>(binUser2);
Console.WriteLine(newUser2);
[MessagePackObject]
public record UserDetailInfo : UserInfo
{
[Key(2)]
public DateTime Birthday { get; set; }
[Key(3)]
public UserAuthInfo? Auth { get; set; }
}
[MessagePackObject]
public record UserAuthInfo
{
[Key(0)]
public string? Code { get; set; }
[Key(1)]
public AuthKind Auth { get; set; }
}
public enum AuthKind
{
None,
User,
Admin
}
출력
["user2","사용자2","1978-08-23T00:00:00.0000000Z",["ADMIN",2]]
UserDetailInfo { Id = user2, Name = 사용자2, DummyValue = 0, Birthday = 1978-08-23 오전 12:00:00, Auth = UserAuthInfo { Code = ADMIN, Auth = Admin } }
상속시 주의해야 할 점은 Key가 부모의 속성과 인덱스가 중복되면 안된다는 점입니다. 속성이 계속 증가할 수 있어서 이 부분이 좀 까다로웠는데요, 저 같은 경우 (0, 1, 2, …) → (100, 101, 102, …) 이런식으로 키를 상속받을 때 백자리 정도로 띄워서 부여했는데, 키의 인덱스가 곧 배열의 인덱스이므로 비워진 위치에 null값이 자동으로 들어가면서 직렬화 데이터의 사이즈를 키우는 문제가 있었는데요, 현재로서는 다음처럼
var lz4Options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray);
var bin = MessagePackSerializer.Serialize(model, lz4Options);
압축 옵션을 줘서 해결하고 있습니다. 다른 좋은 방안을 알고계신 분은 댓글로 알려주시면 감사하겠습니다.
정리
간단히 MessagePack을 이용해 직렬화 하는 방법을 알아보았습니다. 이 외에도 Union
Attribute로 abstract class
에 대한 정확한 복호화도 지원합니다. MessagePack-CSharp에서 좀더 상세한 내용을 살펴보실 수 있습니다.