본문 바로가기
Study/Java

[Java] Json Data를 리스트로 변환하자 with Jackson

by 검프 2021. 8. 20.

나라별 백신 접종률 데이터를 JSON으로 가져왔는데, 데이터양이 엄청나게 무시무시해요. (현재 기준 약 3만 개)
또한 JSON Data를 자세히 보면 컬렉션 안에 컬렉션이 있다는 것을 확인할 수 있어요.
저는 이 많은 데이터 중 오직 country 필드의 값이 World인 데이터만 가져오려고 해요. 어떻게 해야 할까요?

[
  {
    "country": "Afghanistan",
    "iso_code": "AFG",
    "data": [
      {
        "date": "2021-02-22",
        "total_vaccinations": 0,
        "people_vaccinated": 0,
        "total_vaccinations_per_hundred": 0.0,
        "people_vaccinated_per_hundred": 0.0
      },
      {
        "date": "2021-02-23",
        "daily_vaccinations": 1367,
        "daily_vaccinations_per_million": 35
      },
        ...
  },
    ...
  {
    "country": "World",
    "iso_code": "OWID_WRL",
    "data": [
      {
        "date": "2020-12-02",
        "total_vaccinations": 0,
        "people_vaccinated": 0,
        "total_vaccinations_per_hundred": 0.0,
        "people_vaccinated_per_hundred": 0.0
      },
      {
        "date": "2020-12-03",
        "total_vaccinations": 0,
        "people_vaccinated": 0,
        "daily_vaccinations_raw": 0,
        "daily_vaccinations": 0,
        "total_vaccinations_per_hundred": 0.0,
        "people_vaccinated_per_hundred": 0.0,
        "daily_vaccinations_per_million": 0
      },
     ...
   }
] 

첫 번째 실패 - URL 필터 옵션 사용

URL에 필터 옵션이 있지 않을까 했지만, github의 json데이터를 가져오는 것이라 실패.


두 번째 실패 - Jackson 필드 사용

우선 Jackson을 사용하기 위한 dependency를 추가해요.

implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.4'

Jackson을 사용하여 "country" 필드를 가져오는 코드를 작성해보았어요.

JsonNode parent = new ObjectMapper().readTree(rawData);
List<String> country = parent.findValuesAsText("country");

생각나는 대로, "country" 필드의 값들을 가져왔고, country 값이 World라면, "data" 필드의 값을 가져오는 로직을 작성했어요.

JsonNode parent = new ObjectMapper().readTree(rawData);
List<String> countries = parent.findValuesAsText("country");
for (String country : countries) {
    if ("World".equals(country)) {
        JsonNode data = parent.findValue("data");
        List<String> date = data.findValuesAsText("date");
        System.out.println("date = " + date);
        List<String> total_vaccinations = data.findValuesAsText("total_vaccinations");
        System.out.println("total_vaccinations = " + total_vaccinations);
    }
}

결괏값은

date = [2021-02-22, 2021-02-23, 2021-02-24, 2021-02-25, 2021-02-26, 2021-02-27, 2021-02-28
...
total_vaccinations = [0, 8200, 54000, 120000, 240000, 504502, 547901, 573277, 590454, 593313

이상해요. 저는 분명 World의 "data" 필드 값들을 가져오려 했는데, Afghanistan "data" 필드 값들을 가져와요.
그럴 수 밖에 없는 게

JsonNode data = parent.findValue("data");

parent.findValue("data")의 "data"는 전체 Json데이터의 "data" 필드를 타깃으로 하기 때문이에요.
즉, value를 가져오는 부분을 잘못 작성했어요.

그럼 어떻게 가져오면 좋을까요?

Jackson에서 제공하는 객체매핑을 사용해보도록 해요.


성공 - Jackson - Json to Object 사용

객체를 가져오기 위한 DTO를 생성해요.

WorldVaccinationParserResponse.java

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.Collections;
import java.util.List;

public class WorldVaccinationParserResponse {

    private String country;
    @JsonProperty(value = "iso_code")
    private String isoCode;
    private List<WorldVaccinationData> data;

    public WorldVaccinationParserResponse() {
    }

    public WorldVaccinationParserResponse(String country, String isoCode, List<WorldVaccinationData> data) {
        this.country = country;
        this.isoCode = isoCode;
        this.data = data;
    }

    public static WorldVaccinationParserResponse empty() {
        return new WorldVaccinationParserResponse("World", "OWID_WRL", Collections.emptyList());
    }

    public String getCountry() {
        return country;
    }

    public String getIsoCode() {
        return isoCode;
    }

    public List<WorldVaccinationData> getData() {
        return data;
    }
}

중첩된 컬렉션 중 외부 컬렉션, 즉, 모든 country 별 data를 매핑하는 Dto에요

WorldVaccinationData.java

import com.fasterxml.jackson.annotation.JsonProperty;

public class WorldVaccinationData {

    private String date;
    @JsonProperty(value = "total_vaccinations")
    private long totalVaccinations;
    @JsonProperty(value = "people_vaccinated")
    private long peopleVaccinated;
    @JsonProperty(value = "people_fully_vaccinated")
    private long peopleFullyVaccinated;
    @JsonProperty(value = "daily_vaccinations_raw")
    private long dailyVaccinationsRaw;
    @JsonProperty(value = "daily_vaccinations")
    private long dailyVaccinations;
    @JsonProperty(value = "total_vaccinations_per_hundred")
    private double totalVaccinationsPerHundred;
    @JsonProperty(value = "people_vaccinated_per_hundred")
    private double peopleVaccinatedPerHundred;
    @JsonProperty(value = "peopleFullyVaccinated_per_hundred")
    private double peopleFullyVaccinatedPerHundred;
    @JsonProperty(value = "daily_vaccinations_per_million")
    private long dailyVaccinationsPerMillion;

    public WorldVaccinationData() {
    }

    public WorldVaccinationData(String date, long totalVaccinations, long peopleVaccinated, long peopleFullyVaccinated, long dailyVaccinationsRaw, long dailyVaccinations, double totalVaccinationsPerHundred, double peopleVaccinatedPerHundred, double peopleFullyVaccinatedPerHundred, long dailyVaccinationsPerMillion) {
        this.date = date;
        this.totalVaccinations = totalVaccinations;
        this.peopleVaccinated = peopleVaccinated;
        this.peopleFullyVaccinated = peopleFullyVaccinated;
        this.dailyVaccinationsRaw = dailyVaccinationsRaw;
        this.dailyVaccinations = dailyVaccinations;
        this.totalVaccinationsPerHundred = totalVaccinationsPerHundred;
        this.peopleVaccinatedPerHundred = peopleVaccinatedPerHundred;
        this.peopleFullyVaccinatedPerHundred = peopleFullyVaccinatedPerHundred;
        this.dailyVaccinationsPerMillion = dailyVaccinationsPerMillion;
    }

    public String getDate() {
        return date;
    }

    public long getTotalVaccinations() {
        return totalVaccinations;
    }

    public long getPeopleVaccinated() {
        return peopleVaccinated;
    }

    public long getPeopleFullyVaccinated() {
        return peopleFullyVaccinated;
    }

    public long getDailyVaccinationsRaw() {
        return dailyVaccinationsRaw;
    }

    public long getDailyVaccinations() {
        return dailyVaccinations;
    }

    public double getTotalVaccinationsPerHundred() {
        return totalVaccinationsPerHundred;
    }

    public double getPeopleVaccinatedPerHundred() {
        return peopleVaccinatedPerHundred;
    }

    public double getPeopleFullyVaccinatedPerHundred() {
        return peopleFullyVaccinatedPerHundred;
    }

    public long getDailyVaccinationsPerMillion() {
        return dailyVaccinationsPerMillion;
    }
}

WorldVaccinationParserResponse의 인자인 List<WorldVaccinationData> data의 정보를 매핑하기 위한 DTO에요

위의 두 개의 DTO를 통해 중첩 컬렉션의 데이터를 매핑할 수 있어요.

public List<WorldVaccinationParserResponse> toWorldVaccinationParserResponse(String rawData) {
    try {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return Arrays.asList(objectMapper.readValue(rawData, WorldVaccinationParserResponse[].class));
    } catch (JsonProcessingException e) {
        throw new RuntimeJsonMappingException("객체를 매핑할 수 없습니다.");
    }
}

매핑 방법은 간단해요.

objectMapper를 인스턴스화 한 뒤, FAIL_ON_UNKNOWN_PROPERTIES 옵션을 false로 둬요(제공 받는 제이슨 Data의 프로퍼티와 Mapping시 DTO의 프로퍼티가 일치하지 않으면 실패하게 하는 옵션).

그다음 objectMapper.readValue(rawData, WorldVaccinationParserResponse[].class) 를 통해 배열 형식의 객체를 받은 후, Arrays.asList로 컬렉션으로 변경해요.

반환 받은 List를 조적하여 특정 조건일 때만 DTO를 반환하기로 하면 끝!

저는 아래와 같이 사용했어요

for (WorldVaccinationParserResponse worldVaccinationParserResponse : toWorldVaccinationParserResponse(rawData)) {
    if ("World".equals(worldVaccinationParserResponse.getCountry())) {
        return worldVaccinationParserResponse;
    }
}

결론

Json Data 매핑 시, List로 만드는 방법은 TypeReference를 이용하여

objectMapper.readValue(rawData, new TypeReference<List<WorldVaccinationParserResponse>>(){});

와 같이 쓸 수 있는데, 먼저 작성했던 코드처럼 배열로 받고, List로 변환하는 것이 조금 더? 빠르다고 합니다 ㅎㅎ


Refer

https://jobc.tistory.com/139

https://www.python2.net/questions-939778.htm

https://stackoverflow.com/questions/26190851/get-single-field-from-json-using-jackson/26191723

https://stackoverflow.com/questions/63218787/how-to-get-all-values-of-a-key-from-a-nested-json

https://stackoverflow.com/questions/55815069/how-to-parse-nested-value-into-list-from-json-array-using-jackson

https://stackoverflow.com/questions/6349421/how-to-use-jackson-to-deserialise-an-array-of-objects

https://www.baeldung.com/jackson-mapping-dynamic-object

https://stackoverflow.com/questions/39916520/mapping-a-dynamic-json-object-field-in-jackson

https://jaehun2841.github.io/2019/01/28/effective-java-item32/#참고

댓글