Как спарсить Джексоном массив без имени
Пытаюсь получить ответ от погодной апишки апишка на запрос координат города присылает такой ответ
[
{
"name": "Moscow",
"local_names": {
"it": "Mosca",
"zh": "莫斯科",
"lv": "Maskava",
"id": "Moskwa",
"ay": "Mosku",
"lt": "Maskva",
"ascii": "Moscow",
"sh": "Moskva",
"uz": "Moskva",
"st": "Moscow",
"co": "Moscù",
"bo": "མོ་སི་ཁོ།",
"fi": "Moskova",
"wo": "Mosku",
"dz": "མོསི་ཀོ",
"sl": "Moskva",
"ps": "مسکو",
"sc": "Mosca",
"hr": "Moskva",
"kv": "Мӧскуа",
"tk": "Moskwa",
"sr": "Москва",
"mg": "Moskva",
"ko": "모스크바",
"wa": "Moscou",
"sg": "Moscow",
"sv": "Moskva",
"ch": "Moscow",
"mn": "Москва",
"ta": "மாஸ்கோ",
"am": "ሞስኮ",
"an": "Moscú",
"io": "Moskva",
"su": "Moskwa",
"ro": "Moscova",
"oc": "Moscòu",
"feature_name": "Moscow",
"bg": "Москва",
"nn": "Moskva",
"en": "Moscow",
"li": "Moskou",
"ga": "Moscó",
"ak": "Moscow",
"ba": "Мәскәү",
"da": "Moskva",
"so": "Moskow",
"ru": "Москва",
"be": "Масква",
"is": "Moskva",
"eo": "Moskvo",
"cu": "Москъва",
"uk": "Москва",
"cs": "Moskva",
"sk": "Moskva",
"vo": "Moskva",
"ab": "Москва",
"qu": "Moskwa",
"kg": "Moskva",
"cy": "Moscfa",
"az": "Moskva",
"pl": "Moskwa",
"jv": "Moskwa",
"fo": "Moskva",
"ia": "Moscova",
"lg": "Moosko",
"hu": "Moszkva",
"vi": "Mát-xcơ-va",
"eu": "Mosku",
"mt": "Moska",
"nb": "Moskva",
"pt": "Moscou",
"tl": "Moscow",
"ar": "موسكو",
"he": "מוסקווה",
"gv": "Moscow",
"sw": "Moscow",
"de": "Moskau",
"gd": "Moscobha",
"ja": "モスクワ",
"yo": "Mọsko",
"dv": "މޮސްކޯ",
"cv": "Мускав",
"bi": "Moskow",
"fa": "مسکو",
"gn": "Mosku",
"no": "Moskva",
"iu": "ᒨᔅᑯ",
"na": "Moscow",
"bs": "Moskva",
"kk": "Мәскеу",
"ml": "മോസ്കോ",
"zu": "IMoskwa",
"za": "Moscow",
"ca": "Moscou",
"hi": "मास्को",
"ln": "Moskú",
"la": "Moscua",
"mi": "Mohikau",
"av": "Москва",
"gl": "Moscova - Москва",
"es": "Moscú",
"os": "Мæскуы",
"af": "Moskou",
"se": "Moskva",
"ht": "Moskou",
"nl": "Moskou",
"kn": "ಮಾಸ್ಕೋ",
"yi": "מאסקווע",
"ty": "Moscou",
"br": "Moskov",
"el": "Μόσχα",
"ce": "Москох",
"tt": "Мәскәү",
"tr": "Moskova",
"ku": "Moskow",
"te": "మాస్కో",
"ie": "Moskwa",
"sq": "Moska",
"et": "Moskva",
"ss": "Moscow",
"hy": "Մոսկվա",
"tg": "Маскав",
"kl": "Moskva",
"kw": "Moskva",
"mr": "मॉस्को",
"ka": "მოსკოვი",
"ky": "Москва",
"fr": "Moscou",
"my": "မော်စကိုမြို့",
"sm": "Moscow",
"fy": "Moskou",
"mk": "Москва",
"ms": "Moscow",
"th": "มอสโก",
"ug": "Moskwa",
"ur": "ماسکو",
"bn": "মস্কো"
},
"lat": 55.7504461,
"lon": 37.6174943,
"country": "RU",
"state": "Moscow"
}
]
но в ответ получаю такой эксепшен
Exception in thread "main" org.springframework.web.client.RestClientException: Error while extracting response for type [class com.romanperkov.spring.rest.entity.cord] and content type [application/json;charset=utf-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `com.romanperkov.spring.rest.entity.cord` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `com.romanperkov.spring.rest.entity.cord` out of START_ARRAY token
at [Source: (PushbackInputStream); line: 1, column: 1]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:120)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:741)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:674)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:315)
at com.romanperkov.spring.rest.Communication.test(Communication.java:81)
at com.romanperkov.spring.rest.App.main(App.java:35)
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `com.romanperkov.spring.rest.entity.cord` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `com.romanperkov.spring.rest.entity.cord` out of START_ARRAY token
at [Source: (PushbackInputStream); line: 1, column: 1]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:284)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:242)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:105)
... 5 more
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `com.romanperkov.spring.rest.entity.cord` out of START_ARRAY token
at [Source: (PushbackInputStream); line: 1, column: 1]
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1468)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1242)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1190)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeFromArray(BeanDeserializer.java:604)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:190)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:166)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4526)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3521)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:273)
... 7 more
Process finished with exit code 1
Я так понял, что проблема в том что ответ апишки начинается с квадратных скобок , то есть она возвращает массив, но он без имени , как в таких случаях действовать?
класс описывающий сущность которую надо получить из апи респонса
@Getter
@Setter
@NoArgsConstructor
public class cord {
private List<String> name;
}
Решение проблемы заключается в том что бы указать класс как массив
String url="http://api.openweathermap.org/geo/1.0/direct?q=QWERTY&limit=5&appid=fe7e6bcec3fedc63d4a158abd1c3c3eb";
cord[] responseEntity=restTemplate.getForObject
(url.replace("QWERTY","москва"), cord[].class);
System.out.println(responseEntity[0]);
Ответы (2 шт):
Модель
Описываем объект, который мы хотим извлечь
Локализованные названия городов проще вытащить в Map<String, String>
private Map<String, String> localNames;
Для того, чтобы не идти против конвенций и называть переменные CamelCase'ом как положено, а не snake_case'ом как в JSON'e поставим аннотацию над полем с именем поля в JSON'е
@JsonProperty("local_names")
private Map<String, String> localNames;
Чтобы было удобнее работать с локализованными именами, лежащими в Map можно добавить вспомогательный метод извлекающий нужное имя по коду локали
public String getLocalName(String locale){
return localNames.get(locale);
}
Соответственно, чтобы получить французкое название понадобится вызвать:
city.getLocalName("fr")
Класс полностью:
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.HashMap;
import java.util.Map;
public class City {
private String name;
@JsonProperty("local_names")
private Map<String, String> localNames;
private Double lat;
private Double lon;
private String country;
private String state;
public City() {
this.localNames = new HashMap<>();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, String> getLocalNames() {
return localNames;
}
public void setLocalNames(Map<String, String> localNames) {
this.localNames = localNames;
}
public Double getLat() {
return lat;
}
public void setLat(Double lat) {
this.lat = lat;
}
public Double getLon() {
return lon;
}
public void setLon(Double lon) {
this.lon = lon;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getLocalName(String locale){
return localNames.get(locale);
}
}
Извлечение
Самый неприятный момент в извлечении объектов из JSON'а - это параметризируемые типы(они же дженерики)
В нашем случае нам в лоб возвращается список объектов и мы должны как-то определить тип.
Указать List.class мы не можем.
Для таких задач в Jackson есть фабрика типов, которая поможет создать описание типа дженерика на основе класса коллекции и класса вложенного объекта
JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, City.class);
Собственно код, извлекающий объекты из json'а
String json = getSOJsonString(); // получаем JSON
ObjectMapper mapper = new ObjectMapper();
// Так как дженерики нельзя просто указать через `getClass`
// Создаем тип для извлечения коллекции объектов
// с помощью встроенных в Jackson конструкторов типов
JavaType type = mapper.getTypeFactory().constructCollectionType(List.class, City.class);
// маппим одно на другое
List<City> cities = mapper.readValue(json, type);
for (City city: cities) {
// System.out.println(city.getName());
// тут выводим все что нам надо
}
Все извлекается корректно.
Вот пример извлечения из дебаггера:

UPDATE
На момент начала написания ответа кода с RestTemplate в вопросе не было, поэтому дополняю вариант с онным.
Конкретно в случае с RestTemplate тип лучше указывать с помощью вспомогательного класса Spring'а, предназначенного ровно для того же что и вышеописанная фабрика.
В Spring есть класс ParameterizedTypeReference с помощью которого можно описать тип параметризируемого объекта (или дженерика)
Делается это так:
ParameterizedTypeReference<List<City>> type = new ParameterizedTypeReference<List<City>>() {};
при этом данный тип можно пристроить далеко не в каждый метод RestTemplate.
Лучше всего воспользоваться следующим способом:
restTemplate.exchange(myUrl, HttpMethod.GET, null, type);
В итоге код получения и извлечения объектов из REST'сервиса будет выглядеть так:
String url="http://api.openweathermap.org/geo/1.0/direct?q=QWERTY&limit=5&appid=fe7e6bcec3fedc63d4a158abd1c3c3eb";
ParameterizedTypeReference<List<City>> type = new ParameterizedTypeReference<List<City>>() {};
List<City> cities = restTemplate.exchange(url.replace("QWERTY","москва"), HttpMethod.GET, null, type).getBody();
for (City city: cities) {
System.out.println(city);
}
Замечание:
В вашем случае будет только не класс City, а класс coord
У меня не поднялась рука так называть класс, поэтому я взял название из упомянутого мной аналогичного ответа.
Классы надо именовать с большой буквы.
Это конвенция.
Как-то так, наверное...
String url="http://api.openweathermap.org/geo/1.0/direct?q=QWERTY&limit=5&appid=fe7e6bcec3fedc63d4a158abd1c3c3eb";
ParameterizedTypeReference<List<coord>> type = new ParameterizedTypeReference<List<coord>>() {};
List<coord> coords = restTemplate.exchange(url.replace("QWERTY","москва"), HttpMethod.GET, null, type).getBody();
for (coord c: coords) {
System.out.println(c);
}
Вы хотите распарсить объект, а вам приходит список. Передайте TypeReference c типом List<City>, чтобы распарсить список:
ObjectMapper objectMapper = new ObjectMapper();
String content = "[{\"name\": \"Moscow\"}]";
TypeReference<List<City>> type = new TypeReference<>() {};
List<City> cities = objectMapper.readValue(content, type);
System.out.println(cities);