DevLog ๐Ÿ˜ถ

[Spring] Jackson annotation - Serialization ์•Œ์•„๋ณด๊ธฐ ๋ณธ๋ฌธ

Back-end/Spring

[Spring] Jackson annotation - Serialization ์•Œ์•„๋ณด๊ธฐ

dolmeng2 2023. 4. 16. 22:29

๋ฌธ๋“ ์ง๋ ฌํ™” ๊ด€๋ จํ•ด์„œ ๊ถ๊ธˆํ•ดํ•˜๋‹ค๊ฐ€, Jackson ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋Œ€ํ•ด์„œ๋„ ํ•œ ๋ฒˆ ๊ณต๋ถ€ํ•ด๋ณด๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™์•„์„œ ์ž‘์„ฑํ•˜๋Š” ๊ธ€!

์ •๋ง ๊ฐ„๋‹จํ•˜๊ณ  ์–•๊ฒŒ ์ •๋ฆฌํ•  ์˜ˆ์ •์ด๋ผ, ๋‚˜์ค‘์— ๊นŠ๊ฒŒ ๊ณต๋ถ€ํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ํ•œ ๋ฒˆ ๋” ์ž‘์„ฑํ•ด๋ณด์ง€ ์•Š์„๊นŒ ์‹ถ๋‹ค ๐Ÿซ 


 

โœ”๏ธ Serialization

Serialization is taking the properties of an object, and converting them into json string representation.

์ง๋ ฌํ™”๋ž€ ๋ฌด์—‡์ผ๊นŒ? ๊ฐ์ฒด์˜ ์†์„ฑ์„ ๊ฐ€์ ธ์™€์„œ Json ๋ฌธ์ž์—ด ํ‘œํ˜„์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

์ •ํ™•ํ•˜๊ฒŒ๋Š”, ๊ฐ์ฒด๋ฅผ ๋ฐ”์ดํŠธ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์ด๋ฉฐ, ์ง๋ ฌํ™”๋œ ๊ฐ์ฒด๋Š” ๋‚˜์ค‘์— ๋‹ค์‹œ ๋ณต์›์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

public class Crew {
    private String name;
    private int age;
    private String course;
}

 

์ด๋Ÿฌํ•œ ๊ฐ์ฒด๊ฐ€ ์กด์žฌํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์„ ๋•Œ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜•ํƒœ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

{
    "Crew": {
        "name": "์ ธ๋‹ˆ",
        "age": 23,
        "course": "Backend"
    }
}

 

์ง๋ ฌํ™”๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ”์ดํŠธ ๋‹จ์œ„๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํŒŒ์ผ์ด๋‚˜ ๋„คํŠธ์›Œํฌ๋ฅผ ํ†ตํ•ด ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

https://hazelcast.com/glossary/serialization/

 

์œ„ ๊ทธ๋ฆผ์„ ๋ณด๋ฉด ์ดํ•ดํ•˜๊ธฐ๊ฐ€ ์‰ฌ์šธ ๊ฒƒ์ด๋‹ค.

๊ฐ์ฒด๋ฅผ ์ €์žฅํ•˜๊ฑฐ๋‚˜ ์™ธ๋ถ€๋กœ ์ „์†กํ•˜๊ธฐ ์œ„ํ•ด, ๊ฐ์ฒด๋ฅผ ๋ฐ”์ดํŠธ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ์ง๋ ฌํ™”๋ผ๊ณ  ํ•œ๋‹ค.

์ง๋ ฌํ™”๋Š” ์–ธ์ œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ? ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

1. ๋ฉ”์‹œ์ง• ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ๊ฐ์ฒด๋ฅผ ์ „์†กํ•˜๊ธฐ ์œ„ํ•˜์—ฌ

2. REST API ๊ฐ™์€ ์›น ์„œ๋น„์Šค์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋ผ๋ฆฌ ํ†ต์‹ ํ•  ๋•Œ

3. ๋ฐฉํ™”๋ฒฝ์„ ํ†ตํ•ด ์ „์†กํ•  ๋•Œ

4. ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ๋กœ ์ €์žฅํ•  ๋•Œ

5. ๋ฐ์ดํ„ฐ์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์‹๋ณ„ํ•  ๋•Œ (์ด์ „ ์ƒํƒœ์™€ ๋น„๊ตํ•˜์—ฌ ๊ฐ์ฒด์˜ ๋ณ€๊ฒฝ ํŒ๋‹จ)

...

 

๋“ฑ๋“ฑ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ž๋ฐ”์—์„œ ๋ชจ๋“  ํด๋ž˜์Šค๋ฅผ ์ง๋ ฌํ™”ํ•  ์ˆ˜๋Š” ์—†๋‹ค. java.io.Serializable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค์˜ ๊ฐ์ฒด๋งŒ ์ง๋ ฌํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.

public interface Serializable {
}

Serializable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ดํŽด๋ณด์ž.

๋‚ด๋ถ€์ ์œผ๋กœ ์•„๋ฌด ๋ฉ”์„œ๋“œ๋„ ์กด์žฌํ•˜์ง€ ์•Š์ง€๋งŒ, '์ง๋ ฌํ™”๊ฐ€ ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด'๋ผ๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•œ ๋งˆ์ปค์šฉ ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค.

 


 

โœ”๏ธ Jackson

Jackson ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” Java์—์„œ Json์œผ๋กœ ์ง๋ ฌํ™”ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€ํ‘œ์ ์ธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด๋‹ค.

(๋ฌผ๋ก  Json๋ฟ๋งŒ ์•„๋‹ˆ๋ผ XML, YAML, CSV ๋“ฑ, ๋‹ค์–‘ํ•œ ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ง€์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ „์ฒ˜๋ฆฌ ํˆด์ด๋‹ค)

implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.2'

์‹ค์Šต์„ ์œ„ํ•ด, build.gradle์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด๋‘๋„๋ก ํ•˜์ž.

 


๐ŸŒฑ Jackson์˜ ๊ธฐ๋ณธ ์ง๋ ฌํ™” ์ „๋žต

์ผ๋‹จ Jackson์ด ์–ด๋–ค ์‹์œผ๋กœ ์ง๋ ฌํ™”๋ฅผ ์ง„ํ–‰ํ•˜๋Š”์ง€ ๋Œ€๋žต์ ์œผ๋กœ ์‚ดํŽด๋ณด์ž.

๊ธฐ๋ณธ์ ์œผ๋กœ ์–ด๋– ํ•œ ๊ฐ์ฒด๊ฐ€ ์žˆ์„ ๋•Œ, getter๊ฐ€ ์—†๋‹ค๋ฉด public ํ•„๋“œ์— ๋Œ€ํ•ด์„œ๋งŒ ์ง๋ ฌํ™”๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค.

public class Crew {

   public final String publicName;
   protected final String protectedName;
   final String defaultName;
   private final String privateName;

    public Crew(final String publicName, final String protectedName, final String defaultName, final String privateName) {
        this.publicName = publicName;
        this.protectedName = protectedName;
        this.defaultName = defaultName;
        this.privateName = privateName;
    }
}

์œ„์™€ ๊ฐ™์ด public ํ•„๋“œ์ธ publicName๋งŒ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

๋งŒ์•ฝ, public์ด ์•„๋‹Œ ํ•„๋“œ๋ฅผ ์ง๋ ฌํ™” ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด getter๋ฅผ ์—ด์–ด์ค˜์•ผ ํ•œ๋‹ค.

public String getPrivateName() {
    return privateName;
}

privateName์œผ๋กœ getter๋ฅผ ์—ด์–ด๋‘์—ˆ๋”๋‹ˆ ์ง๋ ฌํ™” ๋Œ€์ƒ์— ํฌํ•จ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๋งŒ์•ฝ, ์†Œ์Šค ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์—†๋Š” ์ƒํ™ฉ์ด๋ผ๋ฉด ์ง๋ ฌํ™” ์‹œ ์˜ต์…˜์œผ๋กœ ์ค„ ์ˆ˜๋„ ์žˆ๋‹ค.

final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
String result = objectMapper.writeValueAsString(crew);

์ด๋Ÿฐ ์‹์œผ๋กœ ๋ชจ๋“  ํ•„๋“œ์— ๋Œ€ํ•ด์„œ Visibility๋ฅผ Any๋กœ ์„ค์ •ํ•œ๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ง๋ ฌํ™”๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

(ํ•˜์ง€๋งŒ ๋ณดํ†ต ์ด๋ ‡๊ฒŒ๋Š” ์ž˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๊ฒƒ ๊ฐ™๋‹ค. ๋น„์šฉ์ ์ธ ๋ฌธ์ œ๋„ ์žˆ๊ณ  + ๊ด€๋ฆฌ ํฌ์ธํŠธ์˜ ์ฆ๊ฐ€๋„ ์žˆ๊ณ  + ๋ณดํ†ต ObjectMapper๋Š” ๋นˆ์œผ๋กœ 1๋ฒˆ๋งŒ ๋“ฑ๋กํ•ด์„œ ์‚ฌ์šฉํ•  ํ…๋ฐ ๋ชจ๋“  ๊ฐ์ฒด๋“ค์— ๋Œ€ํ•ด ์ด ์˜ต์…˜์„ ์ง€์ •ํ•  ์ˆ˜๋Š” ์—†์œผ๋‹ˆ๊นŒ...)

 


 

๐ŸŒฑ @JsonGetter

Marker annotation that can be used to define a non-static, no-argument value-returning (non-void) method to be used as a "getter" for a logical property. It can be used as an alternative to more general 
JsonProperty annotation (which is the recommended choice in general case).

Getter means that when serializing Object instance of class that has this method (possibly inherited from a super class), a call is made through the method, and return value will be serialized as value of the property.
๋…ผ๋ฆฌ์  ํ”„๋กœํผํ‹ฐ์˜ "getter"๋กœ ์‚ฌ์šฉํ•  non-staticํ•˜๊ฑฐ๋‚˜ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†์œผ๋ฉด์„œ void ํ˜•์ด ์•„๋‹Œ ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋งˆ์ปค ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค. @JsonProperty์˜ ๋Œ€์•ˆ์œผ๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ, ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง„ ํด๋ž˜์Šค์˜ ๊ฐ์ฒด ์ธ์Šคํ„ด์Šค๋ฅผ ์ง๋ ฌํ™” ํ•  ๋•Œ ์ด ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ํ˜ธ์ถœํ•˜๊ณ , ๋ฐ˜ํ™˜๊ฐ’์„ Json ํ”„๋กœํผํ‹ฐ ๊ฐ’์œผ๋กœ ์ง๋ ฌํ™”ํ•œ๋‹ค๋Š” ์˜๋ฏธ๋ฅผ ๊ฐ€์ง„๋‹ค.

 

๋ฉ”์„œ๋“œ์˜ ๋ฐ˜ํ™˜ ๊ฐ’์„ ์ง๋ ฌํ™” ํ•˜๊ธฐ ์œ„ํ•ด์„œ Jackson์˜ @JsonGetter ์–ด๋…ธํ…Œ์ด์…˜์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด ๋ฉ”์„œ๋“œ๋Š” ์ง๋ ฌํ™” ์ค‘์— ํ˜ธ์ถœ๋˜๋ฉฐ, ํ˜ธ์ถœ๋œ ์ดํ›„ ๋ฐ˜ํ™˜๊ฐ’์€ ์ง๋ ฌํ™”๊ฐ€ ๋œ๋‹ค.

๋˜ํ•œ, @JsonGetter ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋Š” staticํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค.

json์˜ ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ name์˜ ๋ฉ”์„œ๋“œ ์ด๋ฆ„์—์„œ ๋”ฐ์˜ค์ง€๋งŒ, ๋ณ„๋„๋กœ ์ง€์ •๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

 

์˜ˆ์‹œ๋ฅผ ๋“ค์–ด๋ณด์ž. ํฌ๋ฃจ๋“ค์˜ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๋Š” ํด๋ž˜์Šค๊ฐ€ ์žˆ๊ณ , ํ•ด๋‹น ํด๋ž˜์Šค๋Š” ํฌ๋ฃจ๋“ค์ด ์ง€๊ธˆ๊นŒ์ง€ ๋จน์€ ์ ์‹ฌ ๋ฉ”๋‰ด ์ •๋ณด๋ฅผ ๋‹ด๋Š” ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. (์˜ˆ์‹œ๋ฅผ ์œ„ํ•ด์„œ ๋ง๋„ ์•ˆ ๋˜๋Š” ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜์˜€๋‹ค... ^_^)

public class Menu {
    private final String name;
    private final int price;

    public Menu(final String name, final int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }
}

์ง๋ ฌํ™”๋ฅผ ์œ„ํ•ด์„œ๋Š” getter๊ฐ€ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Menu์˜ ํ•„๋“œ๋“ค์— ๋Œ€ํ•œ getter๋Š” ์—ด์–ด๋‘๋„๋ก ํ•˜์ž.

public class Crew {
    private final String name;
    private final int age;
    private final List<Menu> menus;

    public Crew(final String name, final int age, final List<Menu> menus) {
        this.name = name;
        this.age = age;
        this.menus = menus;
    }

    @JsonGetter
    public List<Menu> getMenus() {
        return menus;
    }
}

๊ทธ๋ฆฌ๊ณ , ์ด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ์œ„ํ•œ getMenus ๋ฉ”์„œ๋“œ์— @JsonGetter๋ฅผ ๋ถ™์—ฌ์ฃผ์—ˆ๋‹ค.

์ด๋Ÿฌ๋ฉด Crew ํด๋ž˜์Šค๋ฅผ ์ง๋ ฌํ™” ๋˜๊ณ , ๋ฐ˜ํ™˜๊ฐ’์œผ๋กœ Json์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค€๋‹ค. 

๊ธฐ๋ณธ์ ์œผ๋กœ ํ•ด๋‹น Json ๋ฐฐ์—ด์˜ ์†์„ฑ ์ด๋ฆ„์€ ๋ฉ”์„œ๋“œ ๋„ค์ž„์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฉฐ, getXXX ๋ฉ”์„œ๋“œ์—์„œ get์ด ์ œ๊ฑฐ๋œ๋‹ค.

 

์œ„์˜ ๊ฒฝ์šฐ, getMenus๋ผ๋Š” ๋ฉ”์„œ๋“œ์ด๊ธฐ ๋•Œ๋ฌธ์— get์ด๋ผ๋Š” ๋„ค์ด๋ฐ์ด ์ œ๊ฑฐ๋˜๊ณ , ๊ทธ ๋’ค์— ๋ถ™์€ 'Menus'๊ฐ€ ์นด๋ฉœ ์ผ€์ด์Šค๋กœ ํ˜•์‹์ด ์ง€์ •๋œ ๋‹ค์Œ ํ”„๋กœํผํ‹ฐ ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค. 

 

์ง๋ ฌํ™”๊ฐ€ ์ž˜ ๋˜๋Š”์ง€ ํ•œ ๋ฒˆ ํ™•์ธํ•ด๋ณด์ž.

@Test
public void jsonGetter() throws JsonProcessingException {
    // given
    final List<Menu> menus = List.of(new Menu("๋งˆ๋ผํƒ•", 10000), new Menu("์ œ์œก", 10000));
    final Crew crew = new Crew("์ ธ๋‹ˆ", 23, menus);

    // when
    String result = new ObjectMapper().writeValueAsString(crew);
    System.out.println(result);

    // then
    assertThat(JsonPath.from(result).getList("menus"))
            .isNotNull();
}

ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ, ์„ฑ๊ณต์ด ๋˜์—ˆ๋‹ค.

์ถœ๋ ฅํ–ˆ์„ ๋•Œ๋„ menu์— ๋Œ€ํ•ด ๊ฐ’๋“ค์ด ๊ต‰์žฅํžˆ ์ž˜ ๋‹ด๊ธด ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

(์ฐธ๊ณ ๋กœ, getter๋ฅผ ํ†ตํ•ด ์—ด์–ด์ค€ ๊ฐ’์— ๋Œ€ํ•ด์„œ๋งŒ ์ง๋ ฌํ™”๊ฐ€ ๋œ๋‹ค. ์œ„์—์„œ๋Š” menus์— ๋Œ€ํ•œ getter๋งŒ ์กด์žฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”๋‰ด ์ •๋ณด๋งŒ ๋‚˜์™”๋‹ค.)

์‚ฌ์‹ค getter๋งŒ ์žˆ์–ด๋„ ์ง๋ ฌํ™”๋Š” ์ž˜ ๋˜๊ธฐ ๋•Œ๋ฌธ์— @JsonGetter๋ฅผ ์ด๋Ÿฐ ์‹์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์˜๋ฏธ๊ฐ€ ์—†๋‹ค.

 

public class Crew {
    private final String name;
    private final int age;
    private final List<Menu> menus;

    public Crew(final String name, final int age, final List<Menu> menus) {
        this.name = name;
        this.age = age;
        this.menus = menus;
    }

    @JsonGetter(value = "lunch")
    public List<Menu> getMenus() {
        return menus;
    }
}

 

์œ„์™€ ๊ฐ™์ด JsonGetter์˜ value ๊ฐ’์œผ๋กœ, json์˜ ์ด๋ฆ„์„ ์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

@Test
public void jsonGetter() throws JsonProcessingException {
    // given
    final List<Menu> menus = List.of(new Menu("๋งˆ๋ผํƒ•", 10000), new Menu("์ œ์œก", 10000));
    final Crew crew = new Crew("์ ธ๋‹ˆ", 23, menus);

    // when
    String result = new ObjectMapper().writeValueAsString(crew);
    System.out.println(result);

    // then
    assertThat(JsonPath.from(result).getList("lunch"))
            .isNotNull();
}

๋‹ค์‹œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋Œ๋ ค๋ณด์ž. ์ด๋ฒˆ์—๋Š” lunch๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋‚˜์˜จ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ, ์‚ฌ์‹ค ์ด๊ฒƒ๋„ ์ž˜ ์“ฐ์ง€๋Š” ์•Š๋Š”๋‹ค.

getter์˜ ๋„ค์ด๋ฐ ์ž์ฒด๋ฅผ getLunch๋ผ๊ณ  ํ•œ๋‹ค๋ฉด 'lunch'๋ผ๋Š” ํ•„๋“œ๋กœ ๊ฐ’์ด ๋‚˜์˜ค๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 


๐ŸŒฑ @JsonPropertyOrder

Annotation that can be used to define ordering (possibly partial) to use when serializing object properties. Properties included in annotation declaration will be serialized first (in defined order), followed by any properties not included in the definition. Annotation definition will override any implicit orderings (such as guarantee that Creator-properties are serialized before non-creator properties)

This annotation may or may not have effect on deserialization: for basic JSON handling there is no effect, but for other supported data types (or structural conventions) there may be.
๊ฐ์ฒด ํ”„๋กœํผํ‹ฐ๋ฅผ ์ง๋ ฌํ™”ํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆœ์„œ(๋ถ€๋ถ„์ ์ผ ์ˆ˜ ์žˆ์Œ)๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค. 
์–ด๋…ธํ…Œ์ด์…˜ ์„ ์–ธ์— ํฌํ•จ๋œ ํ”„๋กœํผํ‹ฐ๊ฐ€ ์ •์˜๋œ ์ˆœ์„œ๋Œ€๋กœ ๋จผ์ € ์ง๋ ฌํ™”๋˜๊ณ , ๊ทธ ๋‹ค์Œ ํฌํ•จ๋˜์ง€ ์•Š์€ ํ”„๋กœํผํ‹ฐ๊ฐ€ ์ง„ํ–‰๋œ๋‹ค.

์—ญ์ง๋ ฌํ™”์— ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜๋„ ์žˆ๊ณ , ์•„๋‹ ์ˆ˜๋„ ์žˆ๋‹ค.
๊ธฐ๋ณธ JSON ์ฒ˜๋ฆฌ์˜ ๊ฒฝ์šฐ ์˜ํ–ฅ์„ ๋ฏธ์น˜์ง€ ์•Š์ง€๋งŒ ์ง€์›๋˜๋Š” ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ ์œ ํ˜•(๋˜๋Š” ๊ตฌ์กฐ์  ๊ทœ์น™)์˜ ๊ฒฝ์šฐ ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ๋‹ค.

 

@JsonPropertyOrder๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด json property์˜ ์ˆœ์„œ๋ฅผ ์ง€์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

์ฃผ๋กœ ํด๋ž˜์Šค ๋ ˆ๋ฒจ์— ๋ถ™์ด๋ฉฐ, ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ์ˆœ์„œ๋Œ€๋กœ ์ง๋ ฌํ™”๋  ์ด๋ฆ„์„ ์ง€์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

@JsonPropertyOrder(value = "{age}", alphabetic = true)
public class Crew {
    private final String name;
    private final int age;
    private final List<Menu> menus;

    public Crew(final String name, final int age, final List<Menu> menus) {
        this.name = name;
        this.age = age;
        this.menus = menus;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public List<Menu> getMenus() {
        return menus;
    }
}

์ด๋•Œ, ๋ชจ๋“  ํ•„๋“œ์— ๋Œ€ํ•ด์„œ ์ง€์ •ํ•ด์ฃผ์ง€ ์•Š์•˜๋‹ค๋ฉด ๋‚จ์€ ํ•„๋“œ์— ๋Œ€ํ•ด์„œ ์•ŒํŒŒ๋ฒณ์ˆœ์œผ๋กœ ์ •๋ ฌ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. (alphabetic = true)

์šฐ๋ฆฌ๋Š” ์ด์ œ age -> menus -> name ์ˆœ์œผ๋กœ ๋œจ๊ธฐ๋ฅผ ์›ํ•  ๊ฒƒ์ด๋‹ค.

๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด, ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š”๋Œ€๋กœ ์ˆœ์„œ๊ฐ€ ์ž˜ ์ง€์ •๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

๋ฌธ๋“ ๊ถ๊ธˆํ•ด์„œ value์— ๋“ค์–ด๊ฐ€์•ผ ํ•˜๋Š” ๊ฐ’์ด ํ•„๋“œ๋ช…์ด ์•„๋‹ˆ๋ผ, ์ง๋ ฌํ™” ์‹œ ๋‚˜์™€์•ผ ํ•˜๋Š” ํ•„๋“œ๋ช…์ธ๊ฐ€ ์‹ถ์–ด์„œ ์‹คํ—˜์„ ํ•ด๋ณด์•˜๋‹ค.

@JsonGetter(value = "zebra")
public int getAge() {
    return age;
}

๊ทธ๋ž˜์„œ getAge์— JsonGetter๋ฅผ ํ†ตํ•ด์„œ ์•ŒํŒŒ๋ฒณ ๋งจ ๋’ค๋กœ ๊ฐˆ ์ˆ˜ ์žˆ๋„๋ก zebra๋ผ๋Š” ์ด๋ฆ„์„ ๋ถ€์—ฌํ–ˆ๋‹ค.

ํ™•์ธ ๊ฒฐ๊ณผ, ๊ฐ€์žฅ ๋’ค๋กœ ๊ฐ”๋‹ค.

์ด๋กœ ์ธํ•ด์„œ value ๋‚ด๋ถ€์— ๋“ค์–ด๊ฐ€๋Š” ๊ฐ’์€ ํ•„๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ, ์ง๋ ฌํ™” ์‹œ ๋‚˜์˜ค๋Š” ๊ฐ’์„ ๊ธฐ์ค€์œผ๋กœ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.

 


๐ŸŒฑ JsonRawValue

Marker annotation that indicates that the annotated method or field should be serialized by including literal String value of the property as is, without quoting of characters. This can be useful for injecting values already serialized in JSON or passing javascript function definitions from server to a javascript client.
์–ด๋…ธํ…Œ์ด์…˜์ด ๋‹ฌ๋ฆฐ ๋ฉ”์„œ๋“œ๋‚˜ ํ•„๋“œ๊ฐ€ ๋ฌธ์ž๋ฅผ ๋”ฐ์˜ดํ‘œ๋กœ ๋ฌถ์ง€ ์•Š๊ณ , ํ”„๋กœํผํ‹ฐ์˜ ๋ฆฌํ„ฐ๋Ÿด ๋ฌธ์ž์—ด ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ํฌํ•จํ•ด์„œ ์ง๋ ฌํ™”ํ•˜๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋งˆ์ปค ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค. ์ด๋ฏธ Json์œผ๋กœ ์ง๋ ฌํ™”๋œ ๊ฐ’์„ ์‚ฝ์ž…ํ•˜๊ฑฐ๋‚˜, ์„œ๋ฒ„์—์„œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํ•จ์ˆ˜ ์ •์˜๋ฅผ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ํด๋ผ์ด์–ธํŠธ๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•  ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ด๋ฏธ ์ง๋ ฌํ™”๋œ Json ๊ฐ’์„ ๋ฌธ์ž์—ด๋กœ ํ‘œํ˜„ํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

public class Crew {

    @JsonRawValue
    private String info;

    public String getInfo() {
        return info;
    }

    public void setInfo(final String info) {
        this.info = info;
    }
}
@Test
void jsonRawValue() throws JsonProcessingException {
    // given
    Crew crew = new Crew();
    crew.setInfo("{\"name\":\"์ ธ๋‹ˆ\", \"age\":23}");

    // when
    String result = new ObjectMapper().writeValueAsString(crew);

    // then
    System.out.println(result);
}

ํ…Œ์ŠคํŠธ์˜ ๊ฒฐ๊ณผ๊ฐ’์„ ๋ณด๋ฉด, info๋ผ๋Š” ํ•„๋“œ๋กœ json ๊ฐ’์ด ์˜ˆ์˜๊ฒŒ ๋“ค์–ด๊ฐ„ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๋งŒ์•ฝ @JsonRawValue ์–ด๋…ธํ…Œ์ด์…˜์ด ์—†๋‹ค๋ฉด ๋ฆฌํ„ฐ๋Ÿด๊นŒ์ง€ ์ถœ๋ ฅ๋˜์–ด์„œ ์˜ˆ์˜์ง€๊ฐ€ ์•Š๋‹ค.

 


๐ŸŒฑ JsonRootName

Annotation similar to XmlRootElement, used to indicate name to use for root-level wrapping, if wrapping is enabled. Annotation itself does not indicate that wrapping should be used; but if it is, name used for serialization should be name specified here, and deserializer will expect the name as well.
๋ฃจํŠธ ๋ ˆ๋ฒจ์˜ ๋ž˜ํ•‘์— ์‚ฌ์šฉํ•  ์ด๋ฆ„์„ ๋‚˜ํƒ€๋‚ด๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.
์–ด๋…ธํ…Œ์ด์…˜ ์ž์ฒด๋Š” ๋ž˜ํ•‘์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋‚ด์ง€ ์•Š์ง€๋งŒ, ๋ž˜ํ•‘์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์ง๋ ฌํ™”์— ์‚ฌ์šฉ๋˜๋Š” ์ด๋ฆ„์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์—ญ์ง๋ ฌํ™”์— ๋Œ€ํ•ด์„œ๋„ ์—ฌ๊ธฐ์„œ ์ง€์ •๋œ ์ด๋ฆ„์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์—ฌ๋Ÿฌ ๊ฐœ์˜ Json ๊ฐ’์— ๋Œ€ํ•œ ๋ฃจํŠธ ์ด๋ฆ„์„ ์ง€์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค. ํด๋ž˜์Šค ๋ ˆ๋ฒจ์— ๋ถ™์ด๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.

@JsonRootName("lunchMenu")
public class Crew {

    private final List<Menu> menus;

    public Crew(final List<Menu> menus) {
        this.menus = menus;
    }

    public List<Menu> getMenus() {
        return menus;
    }
}
@Test
void jsonRootName() throws JsonProcessingException {
    // given
    final List<Menu> menus = List.of(new Menu("๋งˆ๋ผํƒ•", 10000), new Menu("์ œ์œก", 10000));
    final Crew crew = new Crew(menus);

    // when
    final ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
    String result = objectMapper.writeValueAsString(crew);

    // then
    System.out.println(result);
}

์ฐธ๊ณ ๋กœ, objectMapper.enable(SerializationFeature.WRAP_ROOT_VALUE); ์ด ์ฝ”๋“œ๋ฅผ ์ง€์ •ํ•ด์ค˜์•ผ wrapping ๋œ๋‹ค.

์ด๋Ÿฐ ์‹์œผ๋กœ lunchMenu๋ผ๋Š” ์ตœ์ƒ์œ„ ๊ฐ’์œผ๋กœ ๊ฐ์‹ธ์ ธ ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๋งŒ์•ฝ WRAP_ROOT_VALUE ์˜ต์…˜์„ ์ง€์ •ํ–ˆ์ง€๋งŒ @JsonRootName์„ ์ง€์ •ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

์ด๋ ‡๊ฒŒ ํด๋ž˜์Šค ์ด๋ฆ„์ธ Crew๋กœ ๊ฐ์‹ธ์ง€๊ฒŒ ๋œ๋‹ค.


 

๐ŸŒฑ @JsonValue

Marker annotation similar to XmlValue that indicates that results of the annotated "getter" method (which means signature must be that of getters; non-void return type, no args) is to be used as the single value to serialize for the instance. Usually value will be of a simple scalar type (String or Number), but it can be any serializable type (Collection, Map or Bean).
์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์ง€์ •๋œ "getter" ๋ฉ”์„œ๋“œ์˜ ๊ฒฐ๊ณผ (getXXX, void๊ฐ€ ์•„๋‹Œ ๋ฐ˜ํ™˜ ๊ฐ’, ์ธ์ž๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ)์˜ ์ธ์Šคํ„ด์Šค์— ๋Œ€ํ•ด ์ง๋ ฌํ™”ํ•  ๊ฐ’์„ ๋‚˜ํƒ€๋‚ด๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ๊ฐ’์€ ๋‹จ์ˆœํ•œ ๋ฌธ์ž์—ด์ด๋‚˜ ์ˆซ์ž์ง€๋งŒ, ์ง๋ ฌํ™” ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ํƒ€์ž… (์ปฌ๋ ‰์…˜, ๋งต, ๋นˆ)์ด ๋  ์ˆ˜ ์žˆ๋‹ค.
At most one method of a Class can be annotated with this annotation; if more than one is found, an exception may be thrown. Also, if method signature is not compatible with Getters, an exception may be thrown (whether exception is thrown or not is an implementation detail (due to filtering during introspection, some annotations may be skipped) and applications should not rely on specific behavior).
ํด๋ž˜์Šค์˜ ๋ฉ”์„œ๋“œ์—๋Š” ์ด ์–ด๋…ธํ…Œ์ด์…˜์„ ์ตœ๋Œ€ 1๊ฐœ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, 2๊ฐœ ์ด์ƒ ์‚ฌ์šฉ ์‹œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
๋˜ํ•œ, ๋ฉ”์„œ๋“œ ์ด๋ฆ„์ด getXXX์˜ ํ˜•ํƒœ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

 

์ธ์Šคํ„ด์Šค์˜ JSON ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

๊ฐ์ฒด ์ „์ฒด๋ฅผ ์ง๋ ฌํ™”ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ, annotating method๋ฅผ ํ˜ธ์ถœํ•˜๋ผ๋Š” ์˜๋ฏธ๋กœ ์“ฐ์ธ๋‹ค.

ํ•˜๋‚˜์˜ ๋ฉ”์„œ๋“œ์—๋งŒ ๋‹ฌ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋‘˜ ์ด์ƒ์˜ ๋ฉ”์„œ๋“œ์— ์ด ์–ด๋…ธํ…Œ์ด์…˜์ด ๋‹ฌ๋ ค์žˆ์œผ๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

full JSON string์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์—†์œผ๋ฉฐ, ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด map ๊ฐ™์€ ์ง๋ ฌํ™” ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•ด์•ผ ํ•œ๋‹ค.

 

๋ง๋กœ ์„ค๋ช…ํ•˜๋ฉด ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์—, ์ฝ”๋“œ๋กœ ๋ณด๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

public class Crew {

    private final String name;
    private final Course course;

    public Crew(final String name, final Course course) {
        this.name = name;
        this.course = course;
    }

    public String getName() {
        return name;
    }
    
    public Course getCourse() {
        return course;
    }

    public enum Course {
        FRONTEND("ํ”„๋ก ํŠธ", 50), BACKEND("๋ฐฑ์—”๋“œ", 100), ANDROID("์•ˆ๋“œ๋กœ์ด๋“œ", 25);

        private final String name;
        private final int count;

        Course(final String name, final int count) {
            this.name = name;
            this.count = count;
        }

        @JsonValue
        public String getName() {
            return name;
        }

        public int getCount() {
            return count;
        }
    }
}

์ด๋ฒˆ์—๋Š” ํฌ๋ฃจ๋“ค์˜ ๊ณผ์ • ์ •๋ณด๋ฅผ ๋‹ด์€ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•˜์˜€๋‹ค.

๊ทธ๋ฆฌ๊ณ , ๊ณผ์ •์˜ ์ด๋ฆ„์„ ๋ฐ˜ํ™˜ํ•˜๋Š” getter ๋ฉ”์„œ๋“œ์— @JsonValue๋ฅผ ๋‹ฌ์•„์ฃผ์—ˆ๋‹ค.

Crew์— ๋Œ€ํ•œ ์ง๋ ฌํ™” ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด course์—์„œ๋Š” name์— ๋Œ€ํ•œ ์ •๋ณด๋งŒ ๋‚˜์˜ค๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

๋งŒ์•ฝ, @JsonValue๋ฅผ ๋ถ™์ด์ง€ ์•Š์•˜๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋‚˜์˜ฌ๊นŒ?

enum์—๋Š” 2๊ฐœ์˜ getter๊ฐ€ ์žˆ์Œ์—๋„ enum์˜ name ๊ฐ’์ด ๋””ํดํŠธ๋กœ ๋‚˜์˜ค๊ฒŒ ๋œ๋‹ค.

(์ด ๋ถ€๋ถ„์€ ์ดํŒฉํ‹ฐ๋ธŒ์ž๋ฐ”์—์„œ ์‹ฑ๊ธ€ํ„ด - enum์„ ์‚ฌ์šฉํ•˜๋ผ๋Š” ํฌ์ŠคํŒ…์—์„œ ์ฝ”๋“œ ๋ ˆ๋ฒจ๋กœ ๋‹ค๋ฃจ์—ˆ๋‹ค.)

 

๋งŒ์•ฝ name๊ณผ course ๋‘ ๊ฐ€์ง€ ๋ชจ๋‘์—๋‹ค๊ฐ€ @JsonValue๋ฅผ ๋ถ™์ด๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

์ด๋Ÿฐ ์‹์œผ๋กœ ์—ฌ๋Ÿฌ ๊ฐœ์˜ value์— ๋ถ™์–ด์žˆ๋‹ค๊ณ  ์•ˆ ๋œ๋‹ค๊ณ  ๋‚˜์˜จ๋‹ค.

 

ํ•˜์ง€๋งŒ, ๋‚˜๋Š” enum์˜ 2๊ฐ€์ง€ ๊ฐ’ ๋ชจ๋‘๋ฅผ ๋ณด๊ณ  ์‹ถ์„ ์ˆ˜๋„ ์žˆ๋‹ค.

public class Crew {

    ...

    public enum Course {
        FRONTEND("ํ”„๋ก ํŠธ", 50), BACKEND("๋ฐฑ์—”๋“œ", 100), ANDROID("์•ˆ๋“œ๋กœ์ด๋“œ", 25);

        private final String name;
        private final int count;

        Course(final String name, final int count) {
            this.name = name;
            this.count = count;
        }

        @JsonValue
        public Map<String, Object> toJson() {
            final Map<String, Object> values = new HashMap<>();
            values.put("name", name);
            values.put("count", count);
            return values;
        }

        ...
    }
}

๊ทธ๋ ‡๋‹ค๋ฉด ์ด๋Ÿฐ ์‹์œผ๋กœ Map์„ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

๊ทธ๋Ÿฌ๋ฉด ์ด๋ ‡๊ฒŒ name๊ณผ course์˜ ๋ชจ๋“  ์ •๋ณด์— ๋Œ€ํ•ด์„œ ์ž˜ ๋‚˜์˜ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 


 

๐ŸŒฑ @JsonAnyGetter

Marker annotation that can be used to define a non-static, no-argument method or member field as something of a reverse of JsonAnySetter method; basically being used like a getter but such that contents of the returned Map (type must be Map) are serialized as if they were actual properties of the bean that contains method/field with this annotations. As with JsonAnySetter, only one property should be annotated with this annotation.
static์ด ์•„๋‹ˆ๋ฉด์„œ, ์ธ์ž๊ฐ€ ์—†๋Š” ๋ฉ”์„œ๋“œ๋‚˜ ๋ฉค๋ฒ„ ํ•„๋“œ๋ฅผ ์ •์˜ํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•˜๋Š” ๋งˆ์ปค ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.
์ผ๋ฐ˜์ ์œผ๋กœ getter์ฒ˜๋Ÿผ ์‚ฌ์šฉ๋˜์ง€๋งŒ, ์ด ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ”์„œ๋“œ, ํ•„๋“œ๋ฅผ ํฌํ•จํ•˜๋Š” ๋นˆ์˜ ์‹ค์ œ ํ”„๋กœํผํ‹ฐ์ฒ˜๋Ÿผ ๋ฐ˜ํ™˜๋œ Map์˜ ๋‚ด์šฉ์ด ์ง๋ ฌํ™”ํ•˜๋„๋ก ํ•œ๋‹ค. ๋˜ํ•œ, JsonAnySetter์ฒ˜๋Ÿผ ํ•˜๋‚˜์˜ ํ”„๋กœํผํ‹ฐ์—๋งŒ ์ ์šฉํ•ด์•ผ ํ•œ๋‹ค.

 

๋ฐ˜ํ™˜ ๊ฐ’์ด Map์ธ ๋ฉ”์„œ๋“œ๋ฅผ ํ‘œํ˜„ํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•œ๋‹ค.

Map์„ ํ‚ค์™€ ๊ฐ’์˜ ์Œ์œผ๋กœ ํ‰๋ฉดํ™”๋ฅผ ํ•˜๋ฉฐ, root level์ฒ˜๋Ÿผ ๊ฐ’์ด ๋‚˜์˜ค๋„๋ก ๋งŒ๋“ค์–ด์ค€๋‹ค. (ํ•ด๋‹น ํด๋ž˜์Šค์— ์กด์žฌํ•˜๋Š” ํ•„๋“œ์ฒ˜๋Ÿผ)

ํด๋ž˜์Šค๋‹น ํ•˜๋‚˜์˜ Json getter๋งŒ ๊ฐ€๋Šฅํ•˜๋‹ค.

public class Crew {

    private final String name;
    private final Map<String, String> pair;

    public Crew(final String name, final Map<String, String> pair) {
        this.name = name;
        this.pair = pair;
    }

    public String getName() {
        return name;
    }

    @JsonAnyGetter
    public Map<String, String> getPair() {
        return pair;
    }
}

๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด, pair๋กœ ๋“ค์–ด๊ฐ„ map์˜ ์ •๋ณด๊ฐ€ crew์— ์กด์žฌํ•˜๋Š” ํ•„๋“œ์ธ ๊ฒƒ์ฒ˜๋Ÿผ ์ง๋ ฌํ™”๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๋งŒ์•ฝ @JsonAnyGetter๋ฅผ ์ œ๊ฑฐํ•˜์˜€๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‚˜ํƒ€๋‚ฌ์„ ๊ฒƒ์ด๋‹ค.

 

์œ„์—์„œ๋Š” ์˜ˆ์ œ๋ฅผ ์ด๋ ‡๊ฒŒ ํ–ˆ์ง€๋งŒ, ๋ณดํ†ต ์‹ค์ œ๋กœ ์“ฐ์ธ๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์“ฐ์ผ ๊ฒƒ ๊ฐ™๋‹ค.

์•ฝ๊ฐ„ ์ด๋Ÿฐ ์‹์œผ๋กœ... map์˜ ํ‚ค ๊ฐ’์œผ๋กœ ์‹ค์ œ ํ•„๋“œ๊ฐ’์˜ ์ด๋ฆ„์„ ๋„ฃ์–ด์„œ ํ‰๋ฉดํ™”๋ฅผ ํ•  ๊ฒƒ ๊ฐ™๋‹ค.

(์™ธ๋ถ€ api ์ „์†ก ์‹œ ๊ทœ๊ฒฉ์„ ๋งž์ถ”๊ธฐ ์œ„ํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์„ ๊ฒƒ ๊ฐ™๊ณ ... ์‚ฌ์šฉ ์˜ˆ์‹œ ์˜์™ธ๋กœ ๋‹ค์–‘ํ•  ๊ฒƒ ๊ฐ™๋‹ค.)

 


๐ŸŒฑ JsonNaming

Annotation that can be used to indicate a PropertyNamingStrategy to use for annotated class. Overrides the global (default) strategy. Note that if the value() property is omitted, its default value means "use default naming" (that is, no alternate naming method is used.
ํด๋ž˜์Šค์— ๋Œ€ํ•ด PropertyNamingStrategy์„ ๋‚˜ํƒ€๋‚ด๋Š”๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.
๊ธฐ๋ณธ ์ „๋žต์„ ์žฌ์ •์˜ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋ฉฐ, value() ์†์„ฑ์„ ์ƒ๋žตํ•˜๋ฉด ๊ธฐ๋ณธ ๋„ค์ด๋ฐ ์ „๋žต์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

 

์ง๋ ฌํ™” ์‹œ ๋„ค์ด๋ฐ ์ „๋žต์„ ๋ฌด์—‡์œผ๋กœ ํ• ์ง€ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. Jackson์€ ํ˜„์žฌ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋„ค์ด๋ฐ ์ „๋žต์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ๋‹ค.

name example
LOWER_CAMEL_CASE nameStrategy, numberValue
UPPER_CAMEL_CASE NameStrategy, NumberValue
SNAKE_CASE name_strategy, number_value
UPPER_SNAKE_CASE Name_Strategy, Number_Value
LOWER_CASE namestrategy, numbervalue
KEBAB_CASE name-strategy, number-value
LOWER_DOT_CASE number.strategy, number.value

๊ทธ๋ž˜์„œ, ๋ณดํ†ต camel Case๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ž๋ฐ”์™€ ์š”์ฒญ API ์‚ฌ์ด์˜ ๊ฐ„๊ทน์„ ๋งž์ถ”๊ธฐ ์œ„ํ•ด์„œ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค.

@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Crew {

   private final String crewName;
   private final int crewAge;

    public Crew(final String crewName, final int crewAge) {
        this.crewName = crewName;
        this.crewAge = crewAge;
    }

    public String getCrewName() {
        return crewName;
    }

    public int getCrewAge() {
        return crewAge;
    }
}

์ด๋Ÿฐ ์‹์œผ๋กœ ํŽธ๋ฆฌํ•˜๊ฒŒ snake case๋กœ ๋ณ€ํ™˜์ด ๊ฐ€๋Šฅํ•˜๋‹ค.


๐ŸŒฑ JsonIgnore / JsonInclude

- JsonIgnore

Marker annotation that indicates that the annotated method or field is to be ignored by introspection-based serialization and deserialization functionality. That is, it should not be consider a "getter", "setter" or "creator".

In addition, starting with Jackson 1.9, if this is the only annotation associated with a property, it will also cause cause the whole property to be ignored: that is, if setter has this annotation and getter has no annotations, getter is also effectively ignored. It is still possible for different accessors to use different annotations; so if only "getter" is to be ignored, other accessors (setter or field) would need explicit annotation to prevent ignoral (usually JsonProperty).
์ง๋ ฌํ™”๋‚˜ ์—ญ์ง๋ ฌํ™” ์‹œ ๋ฌด์‹œ๋˜๋Š” ๊ฒƒ์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋งˆ์ปค ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.
ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์„ ์ง€์ •ํ•œ ๊ฒƒ์ด json ํ”„๋กœํผํ‹ฐ์™€ ์—ฐ๊ด€๋œ ์œ ์ผํ•œ ์–ด๋…ธํ…Œ์ด์…˜์ผ ๊ฒฝ์šฐ ์ „์ฒด ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋ฌด์‹œ๋œ๋‹ค. (1.9 ๋ฒ„์ „๋ถ€ํ„ฐ)
ex. setter์—๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด ์žˆ์ง€๋งŒ, getter์—๋Š” ์—†๋Š” ๊ฒฝ์šฐ ๋ฌด์‹œ
-> ๋งŒ์•ฝ getter๋งŒ ๋ฌด์‹œํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด @JsonProperty ๊ฐ™์€ ๋ช…์‹œ์ ์ธ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

- jsonInclude

Annotation used to indicate when value of the annotated property (when used for a field, method or constructor parameter), or all properties of the annotated class, is to be serialized. Without annotation property values are always included, but by using this annotation one can specify simple exclusion rules to reduce amount of properties to write out.
์–ด๋…ธํ…Œ์ด์…˜์ด ์ง€์ •๋œ ํด๋ž˜์Šค์˜ ๋ชจ๋“  ํ”„๋กœํผํ‹ฐ ๊ฐ’์„ ์ง๋ ฌํ™”ํ•  ์กฐ๊ฑด์„ ๋‚˜ํƒ€๋‚ผ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค. (๊ฐ„๋‹จํ•œ ์ œ์™ธ ๊ทœ์น™ ์ง€์ • ๊ฐ€๋Šฅ)

 

getter๋กœ ์—ด๋ฆฐ ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ์•„๋‹Œ, ์ผ๋ถ€ ํ•„๋“œ์— ๋Œ€ํ•ด์„œ๋งŒ ์ง๋ ฌํ™”๋ฅผ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด @JsonIgnore๋ฅผ ํ†ตํ•ด ์ง๋ ฌํ™”๋ฅผ ํ•˜๊ณ  ์‹ถ์ง€ ์•Š์€ ํ•„๋“œ๋ฅผ ์ง€์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

public class Crew {

    private final String crewName;
    private final int crewAge;

    public Crew(final String crewName, final int crewAge) {
        this.crewName = crewName;
        this.crewAge = crewAge;
    }

    public String getCrewName() {
        return crewName;
    }

    @JsonIgnore
    public int getCrewAge() {
        return crewAge;
    }
}

age์— ๋Œ€ํ•ด ignore๋ฅผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— name์— ๋Œ€ํ•ด์„œ๋งŒ ์ง๋ ฌํ™”๊ฐ€ ๋œ๋‹ค.

 

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Crew {

    private final String crewName;
    private final int crewAge;

    public Crew(final String crewName, final int crewAge) {
        this.crewName = crewName;
        this.crewAge = crewAge;
    }

    public String getCrewName() {
        return crewName;
    }

    public int getCrewAge() {
        return crewAge;
    }
}

ํ˜น์€, ์กฐ๊ธˆ ๋” ์‘์šฉํ•ด์„œ ์ด๋Ÿฐ ์‹์œผ๋กœ๋„ ์ž‘์„ฑํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค. 

๋น„์–ด์žˆ์ง€ ์•Š์€ ํ•„๋“œ์— ๋Œ€ํ•ด์„œ๋งŒ ์ง๋ ฌํ™”๊ฐ€ ๋˜๋„๋ก ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๋ฉฐ, ๋ณดํ†ต ์‘๋‹ต๊ฐ’์œผ๋กœ ๋ณด๋‚ด์ค„ ๋•Œ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ํŽธ์ด๋‹ค. (๊ฐœ์ธ์  ์ทจํ–ฅ)

// given
final Crew crew = new Crew("", 23);

// when
final ObjectMapper objectMapper = new ObjectMapper();
String result = objectMapper.writeValueAsString(crew);
System.out.println(result);

๊ทธ๋Ÿผ ์ด๋Ÿฐ ์‹์œผ๋กœ ์ด๋ฆ„ ํ•„๋“œ๊ฐ€ ๋น„์–ด์žˆ๋‹ค๋ฉด ๋‚˜์ด๋งŒ ๋‚˜์˜ค๊ฒŒ ๋œ๋‹ค.

์–ด๋–ค ๊ฐ’์ด ์ง€์ •๋˜๋Š”์ง€์— ๋”ฐ๋ผ์„œ ๋‹ฌ๋ผ์ง€๊ธฐ ๋•Œ๋ฌธ์— ํ™œ์šฉํ•˜๊ธฐ๊ฐ€ ์ข‹๋‹ค.

 


์ƒ๊ฐ๋ณด๋‹ค ๋‚ด์šฉ์ด ๊ธธ์–ด์กŒ๋‹ค...

์‚ฌ์‹ค ๋‚ด๋ถ€ ๋™์ž‘ ์ฝ”๋“œ๋ฅผ ๋ณผ๊นŒ ํ•˜๋‹ค๊ฐ€, ์ง๋ ฌํ™”๋Š” ์‘์šฉ์ด ์ค‘์š”ํ•˜์ง€ ๋‚ด๋ถ€ ๋™์ž‘ ์›๋ฆฌ๊ฐ€ ์ค‘์š”ํ•˜์ง€๋Š” ์•Š์„ ๊ฒƒ ๊ฐ™์•„์„œ ๊ทธ๋ƒฅ ๋’€๋‹ค. (๋ณต์žกํ•˜๊ธฐ๋„ ํ•˜๊ณ ...) ๋‹ค์Œ ํฌ์ŠคํŒ…์œผ๋กœ๋Š” ์—ญ์ง๋ ฌํ™”์— ๋Œ€ํ•ด์„œ ๋‹ค๋ฃจ์–ด๋ณผ๊นŒ ํ•œ๋‹ค.

Comments