나라별 백신 접종률 데이터를 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://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/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/#참고
댓글