Skip to content

Exclude fields

때로 동적으로 유저에게 보여주고 싶은 필드들을 조절하고 싶은 경우가 있을 것이다.

그럴때 할 수 있는 방법은 제외할 필드들을 받아와 제외하는 것이다.

이를 동적으로 처리해야하는 경우 Reflection 등의 방법으로 처리가 가능한데 내 아이디어와 코드를 공유하고자 한다.

직접 매칭

내가 첫번째로 생각해 낸 방법은 제공할 데이터 타입과 제외할 필드를 갖고 있는 데이터의 타입을 1:1로 일치시켜 Reflection 없이 값 비교 만으로 null 또는 삭제 처리를 하는 것이다.

앞으로 예제들에서 사용할 타입이다.

kotlin
data class User(
  var charname: String?,
  var gender: String?,
  var age: Int?
)

data class UserExcludeField(
    val charname: Boolean,
    val gender: Boolean,
    val age: Boolen
)

1:1 로 값비교 하는 로직은 다음과 같다.

kotlin
fun User.excludeIntersection(exclude: A_EXCLUDE): A {
            return copy(
                charname = if (exclude.charname) {
                    null
                } else {
                    charname
                },
                gender = if (exclude.gender) {
                    null
                } else {
                    gender
                },
                age = if (exclude.age) {
                    null
                } else {
                    age
                }
            )
        }

@Test
fun test1() {
        val mockDatabaseData = User("캐릭터a", "남성", 10)

        // true가 제외 false가 그대로 나가는거
        val excludeAge = UserExcludeField(false, false, true)
        val excludeGenderAge = UserExcludeField(false, true, true)

        val result1 = mockDatabaseData.excludeIntersection(excludeAge)

        assert(result1.charname == mockDatabaseData.charname)
        assert(result1.gender == mockDatabaseData.gender)
        assert(result1.age == null)

        val result2 = mockDatabaseData.excludeIntersection(excludeGenderAge)

        assert(result2.charname == mockDatabaseData.charname)
        assert(result2.gender == null)
        assert(result2.age == null)

        println(result1)
        println(result2)
}

이 코드를 봤을때 그럼 모든 Dto나 엔티티에 다 해줘야하는건가? 라고 할 수 있겠지만 적절히 공통화 한다면 가능하지 않을까 싶다.

Reflection

그 다음으로 생각한 방법은 리플렉션을 사용하는 것이다.

kotlin
fun User.excludeWithReflect(excludes: Set<String>): User {
    val temp = copy()
    val exMembers: List<KCallable<*>> = User::class.memberProperties.filter {
        excludes.contains(it.name)
    }

    this::class.memberProperties.filter {
        exMembers.contains(it)
    }.forEach {
        if (it is KMutableProperty<*>) {
            // 이 부분에서 setter를 호출하기 때문에 프로퍼티들이 var로 강제된다.
            // 따라서 이후에 생성자 등으로 변경하는편이 더 좋을 수도 있을것 같다.
            it.setter.call(temp, null)
        }
    }

    return temp
}

@Test
fun fieldReflect() {
    val mockDatabaseData = User("캐릭터a", "남성", 10)
    val excludeAge = setOf("age")
    val excludeGenderAge = setOf("gender", "age")

    val result1 = mockDatabaseData.excludeWithReflect(excludeAge)

    assert(result1.charname == mockDatabaseData.charname)
    assert(result1.gender == mockDatabaseData.gender)
    assert(result1.age == null)

    val result2 = mockDatabaseData.excludeWithReflect(excludeGenderAge)

    assert(result2.charname == mockDatabaseData.charname)
    assert(result2.gender == null)
    assert(result2.age == null)

    println(result1)
    println(result2)
}

Jackson ObjectMapper Filter

자바의 유명한 라이브러리인 Jackson을 활용한 방법이 있다.

kotlin
fun User.excludeWithJackson(excludes: Array<String>): User {
    val filter  = SimpleBeanPropertyFilter.serializeAllExcept(*excludes)
    val filterProvider = SimpleFilterProvider()
        .addFilter("exFilter", filter)
    val om = jacksonObjectMapper()
        .setFilterProvider(filterProvider)
    return om.convertValue<User>(this)
}

@Test
fun jacksonTest() {
    val mockDatabaseData = User("캐릭터a", "남성", 10)
    val excludeAge = arrayOf("age")
    val excludeGEnderAge = arrayOf("gender", "age")

    val result1 = mockDatabaseData.excludeWithJackson(excludeAge)

    assert(result1.charname == mockDatabaseData.charname)
    assert(result1.gender == mockDatabaseData.gender)
    assert(result1.age == null)

    val result2 = mockDatabaseData.excludeWithJackson(excludeGEnderAge)

    assert(result2.charname == mockDatabaseData.charname)
    assert(result2.gender == null)
    assert(result2.age == null)

    println(result1)
    println(result2)
}

Email: echo.youn@kakao.com