λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
Spring

[Spring] - @JsonProperty, @JsonNaming

by 주발2 2021. 9. 2.
λ°˜μ‘ν˜•

πŸ“Ž  Spring - @JsonProperty, @JsonNaming

μ•ˆλ…•ν•˜μ„Έμš”, μ΄λ²ˆμ— 정리할 λ‚΄μš©μ€ μŠ€ν”„λ§μ—μ„œ @JsonProperty, @JsonNaming μ–΄λ…Έν…Œμ΄μ…˜ μž…λ‹ˆλ‹€.

REST API λ°©μ‹μœΌλ‘œ μ„œλ²„μ™€ ν΄λΌμ΄μ–ΈνŠΈκ°€ 데이터λ₯Ό 톡신을 ν•  λ•Œ JSON ν˜•μ‹μ„ 주둜 μ‚¬μš©ν•©λ‹ˆλ‹€.

μ„œλ²„λ‹¨μ—μ„œλŠ” 카멜 μΌ€μ΄μŠ€(Camel Case) 방식을, ν΄λΌμ΄μ–ΈνŠΈλ‹¨μ—μ„œλŠ” μŠ€λ„€μ΄ν¬ μΌ€μ΄μŠ€(Snake Case) 방식을 μ‚¬μš©ν•©λ‹ˆλ‹€.

카멜 μΌ€μ΄μŠ€(Camel Case)
  - 첫 κΈ€μžλŠ” μ†Œλ¬Έμžλ‘œ, 쀑간 κΈ€μžλ“€μ€ λŒ€λ¬Έμžλ‘œ μ‹œμž‘ν•˜λŠ” ν‘œκΈ°λ²•
  - ex) phoneNumber, postMapping

μŠ€λ„€μ΄ν¬ μΌ€μ΄μŠ€(Snake Case)
  - 언더바(_)κ°€ ν¬ν•¨λœ ν‘œν˜„ 방식
  - ex) phone_number, post_mapping

보톡 μžλ°”λŠ” 카멜 μΌ€μ΄μŠ€λ₯Ό, JSONν˜•μ‹μ€ μŠ€ν…Œμ΄ν¬ μΌ€μ΄μŠ€ λ°©μ‹μœΌλ‘œ ν‘œνŽΈμ„ ν•˜λŠ”κ²Œ μ›μΉ™μž…λ‹ˆλ‹€.

μœ„ λ„€λͺ¨λ°•μŠ€μ²˜λŸΌ ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„λ‹¨μ˜ ν‘œν˜„ 방식이 닀름에 따라 λ°μ΄ν„°μ˜ Keyκ°€ λ‹¬λΌμ§€λŠ” κ²½μš°κ°€ μ‘΄μž¬ν•©λ‹ˆλ‹€.

μ΄λŸ¬ν•œ 문제λ₯Ό ν•΄κ²°ν•  λ•Œ @JsonProperty, @JsonNaming μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

 

 

🎯  Kotlin REST API μ½”λ“œ

μ˜ˆμ œλŠ” Kotlin 기반으둜 μž‘μ„±μ„ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

예제 μ½”λ“œλŠ” κΉƒν—ˆλΈŒ μ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€ :)

 

 

UserRequest

package org.juhyun.kotlinspringboot.model

data class UserRequest (
        var name:String?=null,
        var age:Int?=null,
        var email:String?=null,
        var address:String?=null,
        var phoneNumber:String?=null
)

UserRequest κ°μ²΄λŠ” ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„μ—μ„œ 톡신을 ν•  λ•Œ μ‚¬μš©λ˜λŠ” κ°μ²΄μž…λ‹ˆλ‹€.

ν•΄λ‹Ή λ³€μˆ˜λŠ” 카멜 μΌ€μ΄μŠ€ ν‘œκΈ°λ²•μ„ λ”°λ₯΄κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

 

Controller

package org.juhyun.kotlinspringboot.controller

import org.juhyun.kotlinspringboot.model.UserRequest
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api")
class PostApiController {

    @PostMapping("/post-mapping")
    fun postMapping(): String {
        return "post-mapping"
    }

    @PostMapping("/post-mapping/object")
    fun postMappingObject(@RequestBody userRequest: UserRequest): UserRequest {
        println(userRequest) // json -> object
        return userRequest   // object -> json
    }

}

μ»¨νŠΈλ‘€λŸ¬μ—λŠ” postMappingObject() λ©”μ†Œλ“œμ—μ„œ UserRequest 객체λ₯Ό λ°›μ•„μ„œ μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

Talend API Testerλ₯Ό 톡해 μœ„ APIλ₯Ό ν…ŒμŠ€νŠΈν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

 

Talend API Tester

μœ„ API의 κ²°κ³Όμ—μ„œ ν™•μΈν•˜λ“― phoneNumber λ³€μˆ˜μ—λŠ” 데이터가 μ €μž₯이 λ˜μ§€ μ•Šμ•˜λŠ”λ°μš”, μ΄λŠ” API의 key와 응닡 객체의 ν•„λ“œλͺ…이 λ‹€λ₯΄κΈ° λ•Œλ¬Έμ— λ°œμƒν•˜λŠ” λ¬Έμ œμž…λ‹ˆλ‹€.

μ‹€μ œ μ„œλ²„μ—μ„œμ˜ 결과에도 null둜 좜λ ₯이 λ©λ‹ˆλ‹€.

 

이제 @JsonProperty μ–΄λ…Έν…Œμ΄μ…˜κ³Ό @JsonNaming μ–΄λ…Έν…Œμ΄μ…˜μ„ 톡해 문제λ₯Ό ν•΄κ²°ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

 

 

@JsonProperty

package com.fasterxml.jackson.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Marker annotation that can be used to define a non-static
 * method as a "setter" or "getter" for a logical property
 * (depending on its signature),
 * or non-static object field to be used (serialized, deserialized) as
 * a logical property.
 *<p>
 * Default value ("") indicates that the field name is used
 * as the property name without any modifications, but it
 * can be specified to non-empty value to specify different
 * name. Property name refers to name used externally, as
 * the field name in JSON objects.
 *<p>
 * Starting with Jackson 2.6 this annotation may also be
 * used to change serialization of <code>Enum</code> like so:
 *<pre>
public enum MyEnum {
    {@literal @JsonProperty}("theFirstValue") THE_FIRST_VALUE,
    {@literal @JsonProperty}("another_value") ANOTHER_VALUE;
}
</pre>
 * as an alternative to using {@link JsonValue} annotation.
 *<p>
 * Starting with Jackson 2.12 it is also possible to specify {@code namespace}
 * of property: this property is only used by certain format backends (most
 * notably XML).
 */
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface JsonProperty
{
    /**
     * Special value that indicates that handlers should use the default
     * name (derived from method or field name) for property.
     * 
     * @since 2.1
     */
    public final static String USE_DEFAULT_NAME = "";

    ...

}

@JsonProperty μ–΄λ…Έν…Œμ΄μ…˜μ€ 객체λ₯Ό JSON ν˜•μ‹μœΌλ‘œ λ³€ν™˜ν•  λ•Œ Key의 이름을 μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μœ„μ—μ„œ ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„μ˜ ν‘œκΈ°λ²•μ΄ λ‹¬λΌμ„œ λ°œμƒν•˜λŠ” 문제λ₯Ό @JsonProperty μ–΄λ…Έν…Œμ΄μ…˜μ„ 톡해 ν•΄κ²°ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

package org.juhyun.kotlinspringboot.model

import com.fasterxml.jackson.annotation.JsonProperty

data class UserRequest (
        var name:String?=null,
        var age:Int?=null,
        var email:String?=null,
        var address:String?=null,
        
        @JsonProperty("phone_number")
        var phoneNumber:String?=null // jsonμ—μ„œλŠ” phone_number
)

phoneNumber ν•„λ“œλŠ” 카멜 μΌ€μ΄μŠ€λ‘œ μž‘μ„±μ„ ν•˜κ³ , @JsonProperty μ–΄λ…Έν…Œμ΄μ…˜μ„ 톡해 JSON λ³€ν™˜ μ‹œ 받을 Key의 값을 μ„€μ •ν•©λ‹ˆλ‹€.

μ–΄λ…Έν…Œμ΄μ…˜μ„ μ μš©ν•œ ν›„ API ν…ŒμŠ€νŠΈλ₯Ό 진행해보면 데이터가 μ •μƒμ μœΌλ‘œ λ„˜μ–΄μ˜€λŠ” κ±Έ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

 

 

 

@JsonNaming

package com.fasterxml.jackson.databind.annotation;

import java.lang.annotation.*;

import com.fasterxml.jackson.databind.PropertyNamingStrategy;

/**
 * Annotation that can be used to indicate a {@link PropertyNamingStrategy}
 * to use for annotated class. Overrides the global (default) strategy.
 * Note that if the {@link #value} property is omitted, its default value
 * means "use default naming" (that is, no alternate naming method is used).
 * This can be used as an override with mix-ins.
 * 
 * @since 2.1
 */
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@com.fasterxml.jackson.annotation.JacksonAnnotation
public @interface JsonNaming
{
    /**
     * @return Type of {@link PropertyNamingStrategy} to use, if any; default value of
     *    <code>PropertyNamingStrategy.class</code> means "no strategy specified"
     *    (and may also be used for overriding to remove otherwise applicable
     *    naming strategy)
     */
    public Class<? extends PropertyNamingStrategy> value() default PropertyNamingStrategy.class;
}

ν˜„μž¬ 상황은 UserRequest κ°μ²΄μ—μ„œ ν•˜λ‚˜μ˜ ν•„λ“œλ§Œ 섀정을 ν–ˆμ§€λ§Œ, μ΄λŸ¬ν•œ 섀정을 λ‹€μ–‘ν•œ λ³€μˆ˜μ—μ„œ μ„€μ •ν•˜λ €λ©΄ λͺ¨λ“  ν•„λ“œμ— 

@JsonProperty μ–΄λ…Έν…Œμ΄μ…˜μ„ μ μš©ν•΄μ£Όμ–΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ— 번거둜운 μž‘μ—…μ΄ 될 수 μžˆμŠ΅λ‹ˆλ‹€.

 

μ΄λŸ¬ν•œ λ¬Έμ œλŠ” @JsonNaming μ–΄λ…Έν…Œμ΄μ…˜μ„ 톡해 ν΄λž˜μŠ€μ— μ μš©ν•¨μœΌλ‘œμ¨ ν•΄κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

package org.juhyun.kotlinspringboot.model

import com.fasterxml.jackson.databind.PropertyNamingStrategies
import com.fasterxml.jackson.databind.PropertyNamingStrategy
import com.fasterxml.jackson.databind.annotation.JsonNaming

// JSON 넀이밍 μ „λž΅
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy::class)
// @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) deprecated
data class UserRequest (
        var name:String?=null,
        var age:Int?=null,
        var email:String?=null,
        var address:String?=null,
        var phoneNumber:String?=null
)

β€» 기쑴에 μ‚¬μš©ν•˜λ˜ PropertyNamingStrategy ν΄λž˜μŠ€λŠ” Deprecatedκ°€ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

λ”°λΌμ„œ Kotlinμ—μ„œ Jackson Module의 2.12 λ²„μ „λΆ€ν„°λŠ” PropertyNamingStrategies 클래슀의 μ‚¬μš©μ„ ꢌμž₯ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

ν•΄λ‹Ή λ¬Έμ œμ— λŒ€ν•΄ κΆκΈˆν•˜μ‹œλ©΄ 링크 λ₯Ό μ°Έκ³ ν•΄μ£Όμ„Έμš”!

 

μœ„μ™€ 같이 @JsonNaming μ–΄λ…Έν…Œμ΄μ…˜μ„ μ μš©ν•˜κ³  API ν…ŒμŠ€νŠΈλ₯Ό 해보면 phoneNumber ν•„λ“œμ— μ •μƒμ μœΌλ‘œ 데이터가 λ“€μ–΄μ˜¨ κ±Έ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

{
  "name": "JuHyun",
  "age": 20,
  "email": "a@a.com",
  "address": "Seoul",
  "phone_number": "010-1234-1234"
}

 

 

 

 

References

λ°˜μ‘ν˜•

λŒ“κΈ€