Как десериализовать анонимный массив смешанных типов с Джексоном - PullRequest
2 голосов
/ 30 апреля 2020

В моей программе Java я пытаюсь проанализировать данные, полученные из API Strava.com . Одна из JSON полезных нагрузок, которую я получаю оттуда, выглядит следующим образом:

[
  {"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"}
]

По сути, четыре из этих записей (высота, скорость_открытия, расстояние и время) имеют одинаковую структуру (их поле data) является массивом значений типа double (или целых чисел, которые могут быть проанализированы как значения типа double)), но вторая запись (latlng) имеет слегка отличающуюся структуру для поля data (это массив массивов типа double).

Я знаком с библиотекой Jackson для преобразования между JSON и POJO s, если весь контент назван, но не вижу, как я могу смоделировать вышеуказанную структуру данных для ее десериализации.

Допустим, что вместо данных выше, это выглядело следующим образом:

{
  "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"}
}

Тогда я мог бы определить следующие три класса

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;
}

И затем использовать

objectMapper.readValue(jsonString, Holder.class);

для чтения в этом объекте. Однако, поскольку данные, полученные от Стравы, представляют собой массив, а не объект, я терплю неудачу. Я прочитал статью Baeldung о том, как разархивировать коллекции / массивы , но это предполагает, что все классы в массиве / коллекции одинаковы.

Я думал об определении интерфейса, который будет расширен с помощью двух классов, которые можно найти в массиве, а затем использовать этот механизм:

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);

Но это не работает, так как мне нужно было бы найти какой-то способ, чтобы он мог узнать, когда использовать DoubleData класс и когда использовать CoordinateData класс.

Я уверен, что я не первый, кто пытается использовать данные Strava в Java. Можно ли это сделать?

1 Ответ

2 голосов
/ 01 мая 2020

Если возможно, вы обязательно должны использовать их клиент. Strava API v3 показывает много примеров, как использовать эту API вместе с их моделью.

Если вы хотите реализовать свою собственную модель, вам следует рассмотреть наследование и com.fasterxml.jackson.annotation.JsonTypeInfo, com.fasterxml.jackson.annotation.JsonSubTypes аннотаций. Также, JSON Object с типом latlng содержит список объектов, которые представлены в JSON в форме array. Мы можем справиться с этим, используя аннотацию com.fasterxml.jackson.annotation.JsonFormat. Все вместе дает:

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
}

Над отпечатками кода:

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]]

Вы также должны взглянуть на Google Dev Group и обратиться к этому решению.

...