In my Java
program, I am trying to parse data that I get from Strava.com‘s API. One of the JSON
payloads, I receive from there looks as follows:
[ {"type": "altitude","data": [519.1,519.3,519.3,519.4,519.5],"series_type": "distance","original_size": 5,"resolution": "high"}, {"type": "latlng","data": [[46.01234,6.01234],[46.11234,6.11234],[46.21234,6.21234],[46.31234,6.31234],[46.41234,6.41234]],"series_type": "distance","original_size": 5,"resolution": "high"}, {"type": "velocity_smooth","data": [0.0,0.0,0.0,5.5,5.2],"series_type": "distance","original_size": 5,"resolution": "high"}, {"type": "distance","data": [0.0,8.6,11.8,16.6,20.8],"series_type": "distance","original_size": 5,"resolution": "high"}, {"type": "time","data": [0,1,2,3,4],"series_type": "distance","original_size": 5,"resolution": "high"} ]
Basically, four of these entries (altitude, velocity_smooth, distance and time) have the same structure (their data
field is an array of doubles (or ints that can be parsed as doubles)), but the second entry (latlng) has a slighlty different structure for the data
field (it is a an array of arrays of double).
I am familiar with the Jackson
library to convert between JSON
and POJO
s if all the content is named, but do not see how I can model the above data structure to deserialise it.
Let’s say that instead of the data above, it looked as follows:
{ "altitude": {"data": [519.1,519.3,519.3,519.4,519.5],"series_type": "distance","original_size": 5,"resolution": "high"}, "latlng": {"data": [[46.01234,6.01234],[46.11234,6.11234],[46.21234,6.21234],[46.31234,6.31234],[46.41234,6.41234]],"series_type": "distance","original_size": 5,"resolution": "high"}, "velocity_smooth": {"data": [0.0,0.0,0.0,5.5,5.2],"series_type": "distance","original_size": 5,"resolution": "high"}, "distance": {"data": [0.0,8.6,11.8,16.6,20.8],"series_type": "distance","original_size": 5,"resolution": "high"}, "time": {"data": [0,1,2,3,4],"series_type": "distance","original_size": 5,"resolution": "high"} }
Then I could define the following three classes
import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Value; import java.util.List; @Getter @NoArgsConstructor public class Holder { DoubleData altitude; CoordinateData latlng; @JsonProperty("velocity_smooth") DoubleData velocitySmooth; DoubleData distance; DoubleData time; } @Getter @NoArgsConstructor public class DoubleData { List<Double> data; @JsonProperty("series_type") String seriesType; @JsonProperty("original_size") Integer originalSize; String resolution; } @Getter @NoArgsConstructor public class CoordinateData { List<List<Double>> data; @JsonProperty("series_type") String seriesType; @JsonProperty("original_size") Integer originalSize; String resolution; }
And then use
objectMapper.readValue(jsonString, Holder.class);
to read in that object. However, as the data received from Strava is an array instead of an object, I am failing. I have read Baeldung’s article on how to unmarshal to collections/arrays but that assumes that all classes in the array/collection are the same.
I though about defining an interface which would be extended by the two classes that could be found in the array and then use that mechanism:
public interface Data { } @Getter @NoArgsConstructor public class DoubleData implements Data { String type; List<Double> data; @JsonProperty("series_type") String seriesType; @JsonProperty("original_size") Integer originalSize; String resolution; } @Getter @NoArgsConstructor public class CoordinateData implements Data { String type; List<List<Double>> data; @JsonProperty("series_type") String seriesType; @JsonProperty("original_size") Integer originalSize; String resolution; } Data[] array = objectMapper.readValue(jsonString, Data[].class);
But that doesn’t work, as I would need to find some way to let it find out when to use a DoubleData
class and when to use a CoordinateData
class.
I am sure, I am not the first person trying to use Strava data in Java
. Can this be done?
Advertisement
Answer
If possible, you should definitely use their’s client. Strava API v3 shows many examples how to use this API
together with theirs model.
If you want to implement your own model you should consider inheritance and com.fasterxml.jackson.annotation.JsonTypeInfo
, com.fasterxml.jackson.annotation.JsonSubTypes
annotations. Also, JSON Object
with type latlng
contains list of objects which are represented in JSON
in form of array
. We can handle this using com.fasterxml.jackson.annotation.JsonFormat
annotation. All together gives:
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.File; import java.io.IOException; import java.util.List; import java.util.StringJoiner; public class StravaApp { public static void main(String[] args) throws IOException { File jsonFile = new File("./resource/test.json").getAbsoluteFile(); ObjectMapper mapper = new ObjectMapper(); mapper.readValue(jsonFile, new TypeReference<List<Data>>() {}).forEach(System.out::println); } } @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, visible = true, property = "type") @JsonSubTypes({ @JsonSubTypes.Type(name = "altitude", value = DoubleData.class), @JsonSubTypes.Type(name = "latlng", value = CoordinateData.class), @JsonSubTypes.Type(name = "velocity_smooth", value = DoubleData.class), @JsonSubTypes.Type(name = "distance", value = DoubleData.class), @JsonSubTypes.Type(name = "time", value = DoubleData.class) }) abstract class Data<T> { private String type; @JsonProperty("series_type") private String seriesType; @JsonProperty("original_size") private Integer originalSize; private String resolution; private List<T> data; // getters, setters, toString } class DoubleData extends Data<Double> { } class CoordinateData extends Data<Coordinates> { } @JsonFormat(shape = JsonFormat.Shape.ARRAY) class Coordinates { private double lat; private double lng; // getters, setters, toString }
Above code prints:
Data[type='altitude', seriesType='distance', originalSize=5, resolution='high', data=[519.1, 519.3, 519.3, 519.4, 519.5]] Data[type='latlng', seriesType='distance', originalSize=5, resolution='high', data=[Coordinates[lat=46.01234, lng=6.01234], Coordinates[lat=46.11234, lng=6.11234], Coordinates[lat=46.21234, lng=6.21234], Coordinates[lat=46.31234, lng=6.31234], Coordinates[lat=46.41234, lng=6.41234]]] Data[type='velocity_smooth', seriesType='distance', originalSize=5, resolution='high', data=[0.0, 0.0, 0.0, 5.5, 5.2]] Data[type='distance', seriesType='distance', originalSize=5, resolution='high', data=[0.0, 8.6, 11.8, 16.6, 20.8]] Data[type='time', seriesType='distance', originalSize=5, resolution='high', data=[0.0, 1.0, 2.0, 3.0, 4.0]]
You should also take a look on Google Dev Group and consult this solution.