Tyler Whalen Tyler Whalen - 2 months ago 64
JSON Question

How would I parse this JSON, looks like an array but no "[]"s

I'm using Springboot with RestTemplate to parse JSON. All the resources i'm looking at deal with arrays, but I understand that arrays need brackets ([]).

I have JSON here (https://rsbuddy.com/exchange/summary.json) that feels like it should be an array because it's a list, but it doesn't have brackets, and can't be parsed with the following code:

public List<ItemSummaryContainer> consumeItems() {

ResponseEntity<List<ItemSummaryContainer>> itemSummaryResponse =
restTemplate.exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference<List<ItemSummaryContainer>>() {

});

return itemSummaryResponse.getBody();
}


ItemSummaryContainer class

@JsonIgnoreProperties(ignoreUnknown = true)
public class ItemSummaryContainer {

private int id;
private ItemSummary itemSummary;


public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public ItemSummary getItemSummary() {
return itemSummary;
}
public void setItemSummary(ItemSummary itemSummary) {
this.itemSummary = itemSummary;
}
}


ItemSummary class

@JsonIgnoreProperties(ignoreUnknown = true)
public class ItemSummary {

private Integer id;
private String name;
private Integer members;

public ItemSummary() {

}
public ItemSummary(Integer id, String name, Integer members) {
super();
this.id = id;
this.name = name;
this.members = members;
}

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMembers() {
return members;
}
public void setMembers(int members) {
this.members = members;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ItemSummary other = (ItemSummary) obj;
if (id != other.id)
return false;
if (members != other.members)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}


Stacktrace

java.lang.IllegalStateException: Failed to execute CommandLineRunner
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:801) [spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:782) [spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
at org.springframework.boot.SpringApplication.afterRefresh(SpringApplication.java:769) [spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:314) [spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1185) [spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1174) [spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
at com.tjwhalen.game.Application.main(Application.java:50) [classes/:na]
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
at [Source: java.io.PushbackInputStream@55f8669d; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
at [Source: java.io.PushbackInputStream@55f8669d; line: 1, column: 1]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:228) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:213) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:95) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:884) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:868) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:622) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:580) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:526) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at com.tjwhalen.game.service.dao.ItemSummaryURLConsumer.consumeItems(ItemSummaryURLConsumer.java:25) ~[classes/:na]
at com.tjwhalen.game.service.impl.ItemSummaryRestServiceImpl.getItems(ItemSummaryRestServiceImpl.java:25) ~[classes/:na]
at com.tjwhalen.game.loader.LoadItems.load(LoadItems.java:38) ~[classes/:na]
at com.tjwhalen.game.loader.LoaderRunner.execute(LoaderRunner.java:26) ~[classes/:na]
at com.tjwhalen.game.Application$AppConfig.run(Application.java:78) ~[classes/:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:798) [spring-boot-1.4.0.RELEASE.jar:1.4.0.RELEASE]
... 6 common frames omitted
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
at [Source: java.io.PushbackInputStream@55f8669d; line: 1, column: 1]
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:261) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.DeserializationContext.reportMappingException(DeserializationContext.java:1233) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1121) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1074) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.handleNonArray(CollectionDeserializer.java:328) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:259) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:249) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:26) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3789) ~[jackson-databind-2.8.1.jar:2.8.1]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2913) ~[jackson-databind-2.8.1.jar:2.8.1]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:225) ~[spring-web-4.3.2.RELEASE.jar:4.3.2.RELEASE]
... 19 common frames omitted


Please advise, or ask any needed clarifying questions,

Thanks

Answer

The JSON you are trying to parse is perfectly valid. It's not an array, but it's still valid:

{
  "2": {
    "overall_average": 211,
    "buy_average": 208,
    "members": true,
    "id": 2,
    "name": "Cannonball",
    "sell_average": 210,
    "sp": 5
  },
  "6": {
    "overall_average": 0,
    "buy_average": 0,
    "members": true,
    "id": 6,
    "name": "Cannon base",
    "sell_average": 0,
    "sp": 187500
  },
  "12289": {
    "overall_average": 9999,
    "buy_average": 0,
    "members": false,
    "id": 12289,
    "name": "Mithril platelegs (t)",
    "sell_average": 9999,
    "sp": 2600
  },
  ...
}

The ItemSummary class could be defined as following:

@JsonIgnoreProperties(ignoreUnknown = true)
public class ItemSummary {

    private String id;
    private String name;
    private boolean members;

    // Getters and setters omitted
}

And the whole JSON could be parsed into a Map<String, ItemSummary>. See the details below.

Parsing the JSON into a Map<String, ItemSummary>

With Spring REST Template, use:

RestTemplate restTemplate = new RestTemplate(); 
ResponseEntity<Map<String, ItemSummary>> response = 
        restTemplate.exchange(
            "https://rsbuddy.com/exchange/summary.json", 
            HttpMethod.GET, 
            null, 
            new ParameterizedTypeReference<Map<String, ItemSummary>>() {});

Map<String, ItemSummary> map = response.getBody();

With Jackson's ObjectMapper, you could have:

ObjectMapper mapper = new ObjectMapper();
Map<String, ItemSummary> map = 
    mapper.readValue(new URL("https://rsbuddy.com/exchange/summary.json"),
                     new TypeReference<Map<String, ItemSummary>>() {});

Wrapping the Map<String, ItemSummary> into a class

If you need a wrapper class around the Map<String, ItemSummary>, define it as following:

@JsonIgnoreProperties(ignoreUnknown = true)
public class ItemSummaryContainer {

    private Map<String, ItemSummary> items;

    @JsonCreator
    public ItemSummaryContainer(Map<String, ItemSummary> items) {
        this.items = items;
    }

    // Getters and setters omitted
}

With Spring REST Template, use:

RestTemplate restTemplate = new RestTemplate(); 
ResponseEntity<ItemSummaryContainer> response = 
    restTemplate.exchange(
        "https://rsbuddy.com/exchange/summary.json", 
        HttpMethod.GET, 
        null, 
        new ParameterizedTypeReference<ItemSummaryContainer>() {});

ItemSummaryContainer container = response.getBody();

And with Jackson's ObjectMapper, you would have:

ObjectMapper mapper = new ObjectMapper();
ItemSummaryContainer container = 
    mapper.readValue(new URL("https://rsbuddy.com/exchange/summary.json"),
                     ItemSummaryContainer.class);
Comments