๐Ÿ€์ค‘์ฒฉ๋œ JSON์„ Java Object ๋กœ ๋งคํ•‘ํ•˜๋Š” ๋ฐฉ๋ฒ•2 (With Jackson)

์ตœ์žฌํ˜ยท2022๋…„ 9์›” 15์ผ
3
post-custom-banner

์ด์ „ ํฌ์ŠคํŠธ
์ค‘์ฒฉ๋œ JSON์„ Java Object ๋กœ ๋งคํ•‘ํ•˜๋Š” ๋ฐฉ๋ฒ•1 (With Jackson)


๐Ÿคจ ๋“ค์–ด๊ฐ€๋ฉฐ

์ €๋ฒˆ ํฌ์ŠคํŠธ์—์„œ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ค‘์ฒฉ๋œ JSON์„ ์ž๋ฐ” ์˜ค๋ธŒ์ ํŠธ๋กœ ๋งคํ•‘ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃจ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋ฌธ์ œ๋Š” ์ œ๊ฐ€ ๋งคํ•‘ํ•ด์•ผํ•  JSON์˜ ์ค‘์ฒฉ ๊นŠ์ด๋ฅผ ๋ณด๋ฉด 1~2๋ ˆ๋ฒจ ์ •๋„๊ฐ€ ์•„๋‹ˆ์—ˆ๋‹ค๋Š” ๊ฑฐ์ฃ .

"info": {
        "gameCreation": 1662939391488,
        "mapId": 11,
        "participants": [
            {
                "assists": 3,
                "baronKills": 0,
                "basicPings": 1,
                "bountyLevel": 0,
                "challenges": {
                    "12AssistStreakCount": 0,
                    "abilityUses": 220,                  
                },
                "champExperience": 14906,
                "champLevel": 16,
                "perks": {
                    "statPerks": {
                        "defense": 5003,
                        "flex": 5008,
                        "offense": 5005
                    },
                    "styles": [
                        {
                            "description": "primaryStyle",
                            "selections": [
                                {
                                    "perk": 8010,

(๋‹ค์‹œ ํ•œ๋ฒˆ ๋“ฑ์žฅ )

์ €๋ฒˆ ํฌ์ŠคํŠธ๋ฅผ ์ฝ์œผ์‹  ๋ถ„๋“ค์€ ์•„์‹œ๊ฒ ์ง€๋งŒ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋งคํ•‘ ์‹œ, ๊ฐ ์ค‘์ฒฉ JSON๋งˆ๋‹ค Map<String,Object>ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์—ฌ ๋งคํ•‘ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ค‘์ฒฉ์ด ํ•œ๋‘๊ฐœ ์ •๋„๋ฉด ๊ทธ๋ƒฅ ํŽธํ•˜๊ฒŒ ์“ฐ๊ฒ ์ง€๋งŒ, ์ค‘์ฒฉ depth๊ฐ€ ์—ฌ๋Ÿฌ ๋ ˆ๋ฒจ์ด ๋˜๋ฉด Map ์ธ์Šคํ„ด์Šค๋ฅผ ๊ณ„์† ์ƒ์„ฑํ•ด์•ผํ•˜๋Š” ๋‹จ์ ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํšจ์œจ์ ์ธ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋ฒ•์ด ์•„๋‹ˆ๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. (์ œ ์ƒ๊ฐ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ํฐ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด ๋Œ“๊ธ€๋กœ ์•Œ๋ ค์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.)

๋”ฐ๋ผ์„œ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ œ๊ฐ€ ์ค‘์ฒฉ๋œ JSON์„ JAVA ์˜ค๋ธŒ์ ํŠธ๋กœ ๋งคํ•‘ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด 3๊ฐ€์ง€ ์ •๋„๊ฐ€ ์žˆ๋‹ค๊ณ  ๋ง์”€๋“œ๋ ธ์—ˆ๋Š”๋ฐ, ์ด๋ฒˆ์—๋Š” ๊ทธ ์ค‘ ๋‘๋ฒˆ์งธ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์„ค๋ช…ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. 3๋ฒˆ์งธ ๋ฐฉ๋ฒ•์ธ JsonDeserializer๋ฅผ ์ด์šฉํ•œ ๋งคํ•‘์€, ์ง€๊ธˆ ๋‹ค๋ฃฐ JsonNode ๋งคํ•‘๊ณผ ์ƒ๋‹นํžˆ ์œ ์‚ฌํ•˜์—ฌ ๋‹ค๋ฃจ์ง€ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค.

(์Šคํฌํ•˜์ž๋ฉด ๊ฒฐ๋ก ๋ถ€์—์„œ ์ƒ๊ฐ์€ ๋ฐ”๋€Œ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค..)

<๋งคํ•‘ํ•˜๋Š” ๋ฐฉ๋ฒ• 3๊ฐ€์ง€>

  1. ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•œ ๋งคํ•‘
  2. JsonNode๋ฅผ ์‚ฌ์šฉํ•œ ๋งคํ•‘ -> ์ด๋ฒˆ ํฌ์ŠคํŠธ!
  3. ์‚ฌ์šฉ์ž๊ฐ€ ์ •์˜ํ•œ JsonDeserializer๋ฅผ ์ด์šฉํ•œ ๋งคํ•‘

JSON ์†Œ์Šค์ฝ”๋“œ

{
    "nation": "Korea",
    "name": "Minsu",
    "address": {
        "city": "Seoul",
        "gu": "Songpa",
        "apart": {
            "apartName": "JsonApart",
            "dong": "101",
            "ho": "1501"
        }
    }  
}

๋งคํ•‘๋  JAVA Object

public class Person {

    private String nation; //๊ตญ์ 
    private String name; // ์‚ฌ๋žŒ ์ด๋ฆ„
    private String city; // ์‚ฌ๋Š” ๋„์‹œ
    private String apartName; // ์•„ํŒŒํŠธ ์ด๋ฆ„
    
}

์ €๋ฒˆ๊ณผ ๋˜‘๊ฐ™์ด, ํ•œ๊ตญ ๊ตญ์ ์˜ ์„œ์šธ์‹œ ์†กํŒŒ๊ตฌ JsonApart 101๋™ 1501ํ˜ธ ์‚ฌ๋Š” ๋ฏผ์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜ค๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ฐ์ฒด ๋˜ํ•œ Person ๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.


JsonNode๋ฅผ ์‚ฌ์šฉํ•œ ๋งคํ•‘

ObjectMapper์˜ readTree๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ฝ”๋“œ ๋จผ์ € ๋ณด์‹œ์ฃ 

@Test
public void whenUsingJsonNode_thenOk() throws IOException {

	String SOURCE_JSON = "{"nation": "Korea","name": "Minsu","address": {"city": "Seoul","gu": "Songpa","apart": {"apartName": "JsonApart","dong": "101","ho": "1501"}}}";
    
    JsonNode personNode = new ObjectMapper().readTree(SOURCE_JSON);

    Person person = new Person();
    person.setNation(personNode.get("nation").textValue());
    person.setName(personNode.get("name").textValue());
    person.setCity(personNode.get("address")
      .get("city").textValue());
    person.setApartName(personNode.get("address")
      .get("apart").get("apartName").textValue());
	
    assertEquals(person.getNation(), "Korea");
    assertEquals(person.getName(), "Minsu");
    assertEquals(person.getCity(), "Seoul");
    assertEquals(person.getApartName(), "JsonApart");
}

์ €๋ฒˆ ์ฝ”๋“œ๋ณด๋‹ค ์ง๊ด€์ ์œผ๋กœ ์™€๋‹ฟ๋Š” ๋ถ€๋ถ„์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ค‘๊ฐ„์— setter๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„์€ ๋งŒ์•ฝ Builder ํŒจํ„ด์œผ๋กœ ๋Œ€์ฒด๊ฐ€ ๊ฐ€๋Šฅํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ด ์ฝ”๋“œ์˜ ํ•ต์‹ฌ์€ ์•„๋ฌด๋ž˜๋„ ์ด ๋ถ€๋ถ„์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

JsonNode personNode = new ObjectMapper().readTree(SOURCE_JSON);

์œ„์—์„œ ์‚ฌ์šฉํ•œ readTree ๋ฉ”์†Œ๋“œ์˜ ์›ํ˜•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

public <T extends TreeNode> T readTree(JsonParser p)

JSON์„ ์ฝ์–ด๋“ค์—ฌ, TreeNode์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค. TreeNode๋ž€ JSON์„ ํŠธ๋ฆฌ ํ˜•ํƒœ๋กœ ๋‚˜ํƒ€๋‚ธ ์ผ์ข…์˜ ์ž๋ฃŒ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค.
์ œ๊ฐ€ ์‚ฌ์šฉํ•œ ์ฝ”๋“œ์—์„œ๋Š” TreeNode์˜ ๊ตฌํ˜„์ฒด์ธ JsonNodeํƒ€์ž…์„ ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ๋ณด์‹œ์ฃ 

	Person person = new Person();
    person.setNation(personNode.get("nation").textValue());
    person.setName(personNode.get("name").textValue());
    person.setCity(personNode.get("address")
      .get("city").textValue());

๋จผ์ € ์ƒˆ๋กœ์šด Person ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. JsonNodeํƒ€์ž…์˜ personNode์—์„œ Person ํ•„๋“œ์— ๋งž๋Š” ๋…ธ๋“œ๋“ค์„ get() ๋ฉ”์†Œ๋“œ๋กœ ๊บผ๋‚ด textValue() ๋ฉ”์†Œ๋“œ๋กœ String ํ˜•๋ณ€ํ™˜์„ ํ•ด์ฃผ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.


์–ด๋…ธํ…Œ์ด์…˜ ๋งคํ•‘ vs. JsonNode ๋งคํ•‘

์ด๋ ‡๊ฒŒ ๋‘ ๊ฐ€์ง€์˜ ๋งคํ•‘ ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค.
๋‹ค์‹œ ํ•œ๋ฒˆ ์ฝ”๋“œ๋ฅผ ๋ณด๊ณ , ๊ฐ๊ฐ์˜ ์žฅ๋‹จ์ ์„ ์ƒ๊ฐํ•ด์„œ ์ •๋ฆฌํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  • ์–ด๋…ธํ…Œ์ด์…˜ ๋งคํ•‘ ์ฝ”๋“œ
public class Person {
    private String nation;
    private String name;
    private String city;
    private String apartName;
	
    @SuppressWarnings("unchecked")
    @JsonProperty("address")
    private void unpackNested(Map<String,Object> address) {
        this.city = (String)address.get("city");
        Map<String,String> apart = (Map<String,String>)address.get("apart");
        this.apartName = apart.get("apartName");
    }
}
  • ์–ด๋…ธํ…Œ์ด์…˜ ๋งคํ•‘ ์‹คํ–‰ ์ฝ”๋“œ
@Test
public void whenUsingAnnotations_thenOk() throws IOException {
    Product product = new ObjectMapper()
      .readerFor(Product.class)
      .readValue(SOURCE_JSON);	// ๋ฏผ์ˆ˜์˜ JSON์ด ๋„˜์–ด์˜ฌ ์˜ˆ์ •
}
  • JsonNode ๋งคํ•‘ ์ฝ”๋“œ
@Test
public void whenUsingJsonNode_thenOk() throws IOException {

	String SOURCE_JSON = "{"nation": "Korea","name": "Minsu","address": {"city": "Seoul","gu": "Songpa","apart": {"apartName": "JsonApart","dong": "101","ho": "1501"}}}";
    
    JsonNode personNode = new ObjectMapper().readTree(SOURCE_JSON);

    Person person = new Person();
    person.setNation(personNode.get("nation").textValue());
    person.setName(personNode.get("name").textValue());
    person.setCity(personNode.get("address")
      .get("city").textValue());
    person.setApartName(personNode.get("address")
      .get("apart").get("apartName").textValue());

}

โœจ ์–ด๋…ธํ…Œ์ด์…˜ ๋งคํ•‘ ์žฅ์ 

  1. 0๋ ˆ๋ฒจ depth์˜ ์ค‘์ฒฉ์€ ์ฒ˜๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค (ObjectMapper๊ฐ€ ์•Œ์•„์„œ ํ•ด์คŒ).
  2. ํ•œ๋ฒˆ JAVA Object์— ์ •์˜ํ•ด๋†“์œผ๋ฉด ๊ทธ ๋‹ค์Œ๋ถ€ํ„ฐ๋Š” ObjectMapper.readValue๋กœ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
  3. ์ค‘๋ณต๋˜๋Š” ์ฝ”๋“œ๋Š” ์–ด๋…ธํ…Œ์ด์…˜ ๋งคํ•‘์ด ๋œํ•˜๋‹ค.

๐Ÿ’ฅ์–ด๋…ธํ…Œ์ด์…˜ ๋งคํ•‘ ๋‹จ์ 

  1. ์ฝ”๋“œ ๋ ˆ๋ฒจ์—์„œ ํ•œ๋ˆˆ์— ๋“ค์–ด์˜ค์ง€ ์•Š๋Š”๋‹ค.
  2. Map ์ธ์Šคํ„ด์Šค๋ฅผ ์ค‘์ฒฉ ๊นŠ์ด๋งˆ๋‹ค ์ƒ์„ฑํ•ด์ค˜์•ผ ํ•œ๋‹ค.
  3. ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ์ œ๋„ค๋ฆญ ํ˜•๋ณ€ํ™˜ ์ผ€์ด์Šค๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. (๊ณ„์† ๊ฒฝ๊ณ ๋„ ๋œจ๊ณ )

โœจJsonNode ๋งคํ•‘ ์žฅ์ 

  1. ์ฝ”๋“œ ๋ ˆ๋ฒจ์—์„œ ์ง๊ด€์ ์œผ๋กœ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋‹ค.
  2. JsonNode ์ธ์Šคํ„ด์Šค ํ•˜๋‚˜๋กœ ๋งคํ•‘์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

๐Ÿ’ฅJsonNode ๋งคํ•‘ ๋‹จ์ 

  1. JAVA Object ํ•„๋“œ ๊ฐœ์ˆ˜๊ฐ€ ๋งŽ์•„์งˆ ์ˆ˜๋ก getter, setter ๋ฐ˜๋ณต์ด ์‹ฌํ•ด์ง„๋‹ค.
  2. 0๋ ˆ๋ฒจ depth์˜ ์ค‘์ฒฉ ๋˜ํ•œ ์ผ์ผ์ด ๋งคํ•‘ํ•ด์ค˜์•ผ ํ•œ๋‹ค.
  3. ๋นŒ๋” ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ์‹œ, setter๊ฐ€ ๊ฐ•์ œ๋  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿคจ ๊ทธ๋ž˜์„œ ๋ญ˜์“ฐ๋ผ๊ณ 

ํ•˜๋‚˜์˜ ํฌ์ŠคํŠธ์—์„œ๋„ ์˜๊ฒฌ์ด ์™”๋‹ค ๊ฐ”๋‹ค ํ•˜๋Š”๋ฐ ์›ƒ๊ธด๋ฐ.. ์ •๋ฆฌํ•˜๊ณ  ๋ณด๋‹ˆ ์–ด๋…ธํ…Œ์ด์…˜ ๋งคํ•‘์ด ๋‚ซ๊ฒ ๋‹ค ์‹ถ์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ Riot Api์—์„œ ์‘๋‹ต์œผ๋กœ ๋ฐ›์•„์™€์„œ ๋งคํ•‘ํ•  Dto ํ•„๋“œ ๊ฐœ์ˆ˜๊ฐ€ ์žฅ๋‚œ์ด ์•„๋‹Œ๋ฐ์š”,
lol
(๋ฌผ๋ก  ๋‹ค์“ฐ์ง„ ์•Š๊ณ  ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ๋ช‡๊ฐœ ์ถ”๋ฆด ์˜ˆ์ •์ž…๋‹ˆ๋‹ค....)
์ด๊ฑฐ๋ฅผ ์–ธ์ œ ๋‹ค MatchNode.set().get()... ์ด๋Ÿฐ ์‹์œผ๋กœ ํ•ด์•ผํ• ์ง€ ๋ชจ๋ฅด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, JsonNode์˜ ์„ฑ๋Šฅ ๋˜ํ•œ ๋ฌด์กฐ๊ฑด Map.get()๋ณด๋‹ค ์ข‹๋‹ค๊ณ  ํ•  ์ˆœ ์—†์œผ๋‹ˆ... (JsonNode๊ฐ€ ์–ด๋–ค ๊ตฌ์กฐ์ธ์ง€๋Š” ํ•œ๋ฒˆ ์ฐพ์•„๋ด์•ผํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.) ํŠนํžˆ ์–ด๋…ธํ…Œ์ด์…˜ ๋งคํ•‘์„ ์‚ฌ์šฉํ•˜๋ฉด 0๋ ˆ๋ฒจ ๊นŠ์ด์˜ ์ค‘์ฒฉ property๋Š” ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์•„๋„ ๋˜๋‹ˆ, ๋ฌด์กฐ๊ฑด ์–ด๋…ธํ…Œ์ด์…˜ ๋งคํ•‘์„ ์จ์•ผํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.


๋งˆ์น˜๋ฉฐ..

๊ทธ๋ž˜๋„ ๋‘˜ ์‚ฌ์ด์˜ ๋น„๊ต๋ฅผ ํ•˜๊ณ  ๋‚˜๋‹ˆ ์ข€ ๋” ์„ ํƒ์— ํ™•์‹ ์ด ์„œ๋Š” ๋Š๋‚Œ์ด๋„ค์š”.. ๋‹คํ–‰์ž…๋‹ˆ๋‹ค!

ํ‹€๋ฆฐ ๋‚ด์šฉ, ๋ง๋ถ™์ผ ๋‚ด์šฉ ์žˆ์œผ์‹œ๋ฉด ๋Œ“๊ธ€๋กœ ๋‹ฌ์•„์ฃผ์„ธ์š”!!
์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!!


๐Ÿ“š Reference

https://www.baeldung.com/jackson-nested-values
https://www.baeldung.com/jackson-annotations

profile
์ž˜๋ชป๋œ ๊ณ ๋ฏผ์€ ์—†์Šต๋‹ˆ๋‹ค
post-custom-banner

2๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2022๋…„ 9์›” 20์ผ

Thanks for sharing the information on how to map nested Java objects using annotations google feud.

1๊ฐœ์˜ ๋‹ต๊ธ€