출처 : https://code-maze.com/csharp-polymorphic-serialization-and-deserialization/
System.Text.Json이란?
.NET개발자가 JSON format을 handling할 수 있는 powerful한 package이다.
System.Text.Json 소개
예시 Class
Member란 base class와 이를 상속받는 Student, Professor class를 예로 살펴보자.
public abstract class Member
{
public string? Name { get; set; }
public DateTime BirthDate { get; set; }
}
public class Student : Member
{
public int RegistrationYear { get; set; }
public List<string> Courses { get; set; } = new List<string>();
}
public class Professor : Member
{
public string? Rank { get; set; }
public bool IsTenured { get; set; }
}
Student, Professor objects를 포함하는 Member base type array를 생성해보자.
var members = new Member[]
{
new Student()
{
Name = "John Doe",
BirthDate = new DateTime(2000, 2, 4),
RegistrationYear = 2019,
Courses = new List<string>()
{
"Algorithms",
"Databases",
}
},
new Professor()
{
Name = "Jane Doe",
BirthDate = new DateTime(1978, 6, 6),
Rank = "Full Professor",
IsTenured = true,
},
new Student()
{
Name = "Jason Doe",
BirthDate = new DateTime(2002, 7, 8),
RegistrationYear = 2020,
Courses = new List<string>()
{
"Databases"
}
}
};
Polymorphic Serialization With System.Text.Json
JsonSerializer의 Serialize함수를 사용하여 array를 serialize해보자.
var options = new JsonSerializerOptions
{
WriteIndented = true
};
var membersJson = JsonSerializer.Serialize<Member[]>(members, options);
결과적으로, 우리는 파생 클래스에서 정의한 property가 포함되지 않은 JSON string을 얻는다.
[
{
"Name": "John Doe",
"BirthDate": "2000-02-04T00:00:00"
},
{
"Name": "Jane Doe",
"BirthDate": "1978-06-06T00:00:00"
},
{
"Name": "Jason Doe",
"BirthDate": "2002-07-08T00:00:00"
}
]
함수는 base class의 properties만 serialize하였다. 이것은 우연히 파생 클래스의 민감정보가 serialize되는 것을 방지한다.
파생클래스의 properties까지 serialize하려면 아래와 같이 member array에서 object array로 수정한다.
var membersJson = JsonSerializer.Serialize<object[]>(members, options);
부모 클래스, 자식 클래스 property가 모두 담긴 JSON string을 확인할 수 있다. 다만, serialized object properties의 순서는 자식클래스 properties에서 부모클래스 properties 순으로 표시된다. Name, BirthDate property가 뒤로 나온다.
[
{
"RegistrationYear": 2019,
"Courses": [
"Algorithms",
"Databases"
],
"Name": "John Doe",
"BirthDate": "2000-02-04T00:00:00"
},
{
"Rank": "Full Professor",
"IsTenured": true,
"Name": "Jane Doe",
"BirthDate": "1978-06-06T00:00:00"
},
{
"RegistrationYear": 2020,
"Courses": [
"Databases"
],
"Name": "Jason Doe",
"BirthDate": "2002-07-08T00:00:00"
}
]
Polymorphic Deserialization With System.Text.Json
serialization case와는 반대로, JSON string을 deserialization하기 위한 단순한 방법은 없다. deserializer는 string으로부터 적합한 type의 object를 찾을 수 없다.
Custom converter가 적합한 type의 object를 찾을 수 있도록 어떻게 할 수 있는가?
한 가지 방법은, object의 특정 properties를 찾는 것이다. 예를들면, Courses property는 Student class에만 존재한다.
또 다른 방법은, class name을 사용하는 것이다. 적합한 class를 명시하기 위해, JSON object안에 discriminator property를 포함할 수 있다. discriminator property는 class정의에 없지만, serialization 동안 만들어지고, deserialization 동안 읽힌다.
JSON string을 적합한 object로 parse하기 위해, custom conver로 마지막 접근방법을 적용해보자.
JsonConverter<T>를 override하는 빈 UniversityJsonConverter를 생성해보자.
public class UniversityJsonConverter : JsonConverter<Member>
{
public override bool CanConvert(Type typeToConvert) =>
typeof(Member).IsAssignableFrom(typeToConvert);
public override Member Read(ref Utf8JsonReader reader,
Type typeToConvert, JsonSerializerOptions options)
{ }
public override void Write(Utf8JsonWriter writer,
Member member, JsonSerializerOptions options)
{ }
}
Overriding the JsonConverter Read Method
deserialization을 수행하기 위해, Read method를 override하자.
public override Member Read(ref Utf8JsonReader reader,
Type typeToConvert, JsonSerializerOptions options)
{
// StartObject token은 JSON object의 시작을 표시한다.
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException();
reader.Read();
if (reader.TokenType != JsonTokenType.PropertyName)
throw new JsonException();
string? propertyName = reader.GetString();
if (propertyName != "MemberType")
throw new JsonException();
reader.Read();
if (reader.TokenType != JsonTokenType.String)
throw new JsonException();
var memberType = reader.GetString();
Member member;
//type discriminator를 읽고, 적합한 Member object를 생성한다.
switch (memberType)
{
case "Student":
member = new Student();
break;
case "Professor":
member = new Professor();
break;
default:
throw new JsonException();
};
while (reader.Read())
{
// object 종료 시, member를 반환한다.
if (reader.TokenType == JsonTokenType.EndObject)
return member;
if (reader.TokenType == JsonTokenType.PropertyName)
{
propertyName = reader.GetString();
reader.Read();
//PropertyName token을 사용하여 property name을 찾는다.
switch (propertyName)
{
case "Name":
member.Name = reader.GetString();
break;
case "BirthDate":
member.BirthDate = reader.GetDateTime();
break;
case "RegistrationYear":
int registrationYear = reader.GetInt32();
if (member is Student)
((Student)member).RegistrationYear = registrationYear;
else
throw new JsonException();
break;
case "Rank":
string? rank = reader.GetString();
if (member is Professor)
((Professor)member).Rank = rank;
else
throw new JsonException();
break;
case "IsTenured":
bool isTenured = reader.GetBoolean();
if (member is Professor)
((Professor)member).IsTenured = isTenured;
else
throw new JsonException();
break;
case "Courses":
if (member is Student)
{
if (reader.TokenType == JsonTokenType.StartArray)
{
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
break;
var course = reader.GetString();
if (course != null)
((Student)member).Courses.Add(course);
}
}
}
else
throw new JsonException();
break;
}
}
}
throw new JsonException();
}
Overriding the JsonConverter Write Method
discriminator property인 MemberType을 먼저 작성하고, MemberType에 따라 properties를 작성한다.
Write 함수는 원하는 순서대로 JSON string을 생성할 수 있게 한다.
public override void Write(Utf8JsonWriter writer, Member member, JsonSerializerOptions options)
{
writer.WriteStartObject();
if (member is Student student)
{
writer.WriteString("MemberType", "Student");
writer.WriteString("Name", member.Name);
writer.WriteString("BirthDate", member.BirthDate);
writer.WriteNumber("RegistrationYear", student.RegistrationYear);
writer.WriteStartArray("Courses");
foreach(var course in student.Courses)
{
writer.WriteStringValue(course);
}
writer.WriteEndArray();
}
else if (member is Professor professor)
{
writer.WriteString("MemberType", "Professor");
writer.WriteString("Name", member.Name);
writer.WriteString("BirthDate", member.BirthDate);
writer.WriteString("Rank", professor.Rank);
writer.WriteBoolean("IsTenured", professor.IsTenured);
}
writer.WriteEndObject();
}
이제 JsonSerializerOptions object 선언에 생성한 custom converter를 사용해보자.
시작부분의 members 초기 array와 동일한 newMembers array를 결과로 확인할 수 있다.
var options = new JsonSerializerOptions
{
Converters = { new UniversityJsonConverter() },
WriteIndented = true
};
var members = new Member[] { ... } // the member array introduced at the beginning of the article
var membersJson = JsonSerializer.Serialize<Member[]>(members, options);
var newMembers = JsonSerializer.Deserialize<Member[]>(membersJson, options);
'C#' 카테고리의 다른 글
C#] MSSQL AES128/256, SHA256 암복호화 어셈블리 DLL 만들기 (0) | 2022.12.09 |
---|---|
C#] Windows 서비스에 설치 관리자 추가 (0) | 2022.11.24 |
C#] System.Text.Json vs Newtonsoft.Json 차이점 비교 (0) | 2022.07.28 |
C#] Benefits of Polymorphism, 다형성 장점 (0) | 2022.07.27 |
C#] Inheritance, Polymorphism, Abstraction, Interface (0) | 2022.07.21 |
댓글