속성 별 직렬화 설정

class Student
{
   //  다른 속성들 생략
   public int? UserId { get; private set; }
}

Id 중에서도 UserId 는 보안 사고날까봐 함부로 노출시키지는 못하겠고, 그렇다고, 이 속성 하나 때문에 Dto 를 줄줄이 만들기는 너무 귀찮습니다.

public record StudentDto(...) // UserId 만 뺀 다른 속성들 잔뜩

또한 C# 단일 언어 시스템에서, 로직이 프론트엔드(데스크탑 앱 포함)에 있는 경우, 백엔드로부터 Dto 를 받아서, 다시 도메인 객체로 변환하는 코드를 일일이 적자니, 쿨해 보이지 않습니다.

static class DtoConversions
{
   public static Student? ToStudent(this StudentDto dto) => // ...
   public static StudentDto? ToDto(this Student obj) => // ...
   public static Teacher? ToTeacher(this TeacherDto dto) => // ...
   public static TeacherDto? ToDto(this Teacher obj) => // ...
   public static Staff? ToStaff(this StaffDto dto) => // ...
   public static StaffDto? ToDto(this Staff obj) => // ...
}

요럴 때 쓸 수 있는 꼼수입니다.

public class UserIdConverter : JsonConverter<int?>
{
   // 역직렬화 시에는 그냥 읽지만
   public override int? Read(ref Utf8JsonReader r, Type t, JsonSerializerOptions o)
      => r.TryGetInt32(out var id) ? id : default(int?);

   // 직렬화 시에는 null => null, n => 0
   public override void Write(Utf8JsonWriter w, int? v, JsonSerializerOptions o)
   {        
       if (v.HasValue)
          w.WriteNumberValue(0);
       else
          w.WriteNullValue();        
   }
}

그리고, 이 컨버터를 UserId 에 적용시킵니다.

public class Student
{
   // ...
   [JsonInclude, JsonConverter(typeof(UserIdConverter))]
   public int? UserId { get; private set; }
}
public class Teacher
{
   // ...
   [JsonInclude, JsonConverter(typeof(UserIdConverter))]
   public int? UserId { get; private set; }
}
public class Staff
{
   // ...
   [JsonInclude, JsonConverter(typeof(UserIdConverter))]
   public int? UserId { get; private set; }
}
// ...

수많은 Dto 와 수 많은 컨버젼 코드를 이 클래스 하나로 퉁칠 수 있게 됐습니다. ^^

사용 코드

var path = // ...
var students = await httpClient.GetFromJsonAsync<Student[]>(path, token);
var userIdsHidden = students.All(s => s.UserId == null || s.UserId == 0); 
Console.WriteLine(userIdsHidden); // true;
7개의 좋아요