Разбор YAML с массивами разных типов - PullRequest
2 голосов
/ 01 ноября 2019

Я пытаюсь прочитать файл YAML и сохранить результат в списке POJO.

Я не могу изменить файл YAML. Я использую Jackson 2.10.0, но я открыт для любой другой версии. Я пытаюсь проанализировать следующий сценарий с Джексоном:

Vehicles-notype.yaml

Транспортные средства - это, в основном, список объектов с некоторыми общими свойствами и некоторыми уникальными для типа транспортного средства.

---
vehicles:
- car:
  make: "Mercedes-Benz"
  model: "S500"
  topSpeed: 250.0
  seatingCapacity: 5
- truck:
  make: "Isuzu"
  model: "NQR"
  payloadCapacity: 7500.0

Желаемый выход

После прочтения файла мне бы хотелось, чтобы, если я проанализировал список, я хотел бы получить:

... App.java:48): -> start()
... App.java:56): class net.jgp.labs.jackson.yaml.lab411_pojos.Car
... App.java:56): class net.jgp.labs.jackson.yaml.lab411_pojos.Truck

The Car и Truck POJO довольно очевидны:

Автомобиль

package net.jgp.labs.jackson.yaml.lab411_pojos;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class Car extends Vehicle {

  private int seatingCapacity;
  private double topSpeed;

  @JsonCreator
  public Car(
      @JsonProperty("make") String make,
      @JsonProperty("model") String model,
      @JsonProperty("seating") int seatingCapacity,
      @JsonProperty("topSpeed") double topSpeed) {
    super(make, model);
    this.seatingCapacity = seatingCapacity;
    this.topSpeed = topSpeed;
  }

  public int getSeatingCapacity() {
    return seatingCapacity;
  }

  public void setSeatingCapacity(int seatingCapacity) {
    this.seatingCapacity = seatingCapacity;
  }

  public double getTopSpeed() {
    return topSpeed;
  }

  public void setTopSpeed(double topSpeed) {
    this.topSpeed = topSpeed;
  }

  public String getType() {
    return "car";
  }

}

Грузовик

package net.jgp.labs.jackson.yaml.lab411_pojos;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class Truck extends Vehicle {

  private double payloadCapacity;

  @JsonCreator
  public Truck(
    @JsonProperty("make") String make, 
    @JsonProperty("model") String model, 
    @JsonProperty("payload") double payloadCapacity) {
      super(make, model);
      this.payloadCapacity = payloadCapacity;
  }

  public double getPayloadCapacity() {
    return payloadCapacity;
  }

  public void setPayloadCapacity(double payloadCapacity) {
    this.payloadCapacity = payloadCapacity;
  }

  @Override
  public String getType() {
    return "truck";
  }

}

Флот

Fleet POJO являетсятакже очевидно.

package net.jgp.labs.jackson.yaml.lab411_pojos;

import java.util.List;

public class Fleet {

  private List<Vehicle> vehicles;

  public void setVehicles(List<Vehicle> vehicles) {
    this.vehicles= vehicles;
  }

  public List<Vehicle> getVehicles() {
    return vehicles;
  }

}

Автомобиль

Vehicle немного сложнее, так как я пытаюсь играть с @JsonTypeInfo и @JsonSubTypes. Вы можете увидеть закомментированный код, который постепенно сводит меня с ума:

package net.jgp.labs.jackson.yaml.lab411_pojos;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(
    use = JsonTypeInfo.Id.CLASS,
    include = JsonTypeInfo.As.EXTERNAL_PROPERTY
//    ,
//    property = "className"
    )

@JsonSubTypes({
  @Type(value = Car.class, name = "car"),
  @Type(value = Truck.class, name = "truck")
})

//@JsonSubTypes({
//    @Type(value = Car.class, name = "car"),
//    @Type(value = Truck.class, name = "truck")
//})
public abstract class Vehicle {
  private String make;
  private String model;

  @JsonProperty("type")
  abstract public String getType();

  public void setType(String type) {};

  protected Vehicle(String make, String model) {
    this.make = make;
    this.model = model;
  }

  public String getMake() {
    return make;
  }

  public void setMake(String make) {
    this.make = make;
  }

  public String getModel() {
    return model;
  }

  public void setModel(String model) {
    this.model = model;
  }
}

Приложение

Наконец, код приложения, который также довольно очевиден.

package net.jgp.labs.jackson.yaml.lab411_read_diff_objects;

import java.io.File;
import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;

import net.jgp.labs.jackson.yaml.lab411_pojos.Fleet;
import net.jgp.labs.jackson.yaml.lab411_pojos.Vehicle;

/**
 * What does it do?
 * 
 * @author jgp
 */
public class ReadListVehicleNoTypeApp {
  private static final Logger log =
      LoggerFactory.getLogger(ReadListVehicleNoTypeApp.class);

  /**
   * main() is your entry point to the application.
   * 
   * @param args
   */
  public static void main(String[] args) {
    ReadListVehicleNoTypeApp app = new ReadListVehicleNoTypeApp();
    try {
      app.start();
    } catch (JsonProcessingException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  /**
   * The processing code.
   * 
   * @throws IOException
   */
  protected boolean start() throws IOException {
    log.debug("-> start()");

    ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
    Fleet fleet = mapper.readValue(new File("data/vehicles-notype.yaml"),
        Fleet.class);
    for (Vehicle v : fleet.getVehicles()) {
      log.debug("{}", v.getClass());
    }

    return true;
  }
}

Я почти уверен, что есть что поиграть с семейством атрибутов @Json, но я постепенно теряю это; -).

1 Ответ

1 голос
/ 02 ноября 2019

car и truck - это имена полей, свойства. Я не в курсе аннотации Jackson, которая позволяет устанавливать типы из разных полей.

Если файл Yaml не может быть изменен, мы можем использовать Streaming API для чтения свойства типа и десериализации Vehicle. В псевдокоде это может выглядеть так:

while token != EOF
    while token != FIELD_NAME
        nextToken()

    fieldName = nextFieldName();
    clazz = convertToClass(fieldName);
    vehicles.add(read(clazz));

К счастью, имя поля, которое определяет тип, является первым именем поля, и мы можем прочитать его вручную и использовать Jackson для чтения типа после этого. Я удалил аннотации JsonSubTypes и JsonTypeInfo из класса Vehicle, а для Streaming API это может выглядеть следующим образом:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLParser;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class YamlApp {

    public static void main(String[] args) throws Exception {
        File yamlFile = new File("./resource/test.yaml").getAbsoluteFile();

        FleetDeserializer deserializer = new FleetDeserializer();
        Fleet fleet = deserializer.readValue(yamlFile);

        System.out.println(fleet);
    }
}

class FleetDeserializer {
    private YAMLFactory factory = new YAMLFactory();
    private ObjectMapper mapper = new ObjectMapper(factory);

    public Fleet readValue(File yamlFile) throws IOException {
        Fleet fleet = new Fleet();
        fleet.setVehicles(new ArrayList<>());

        YAMLParser parser = factory.createParser(yamlFile);
        while (parser.nextToken() != null) {
            if (parser.getCurrentToken() != JsonToken.START_OBJECT) {
                continue;
            }
            // skip everything until a field name
            while (parser.nextToken() != JsonToken.FIELD_NAME) ;

            Class<? extends Vehicle> type = getType(parser.getCurrentName());
            if (type == null) {
                continue;
            }

            // skip field name
            parser.nextToken();
            parser.nextToken();

            // read next vehicle
            fleet.getVehicles().add(mapper.readValue(parser, type));
        }

        return fleet;
    }

    private Class<? extends Vehicle> getType(String fieldName) {
        Objects.requireNonNull(fieldName);
        switch (fieldName) {
            case "car":
                return Car.class;
            case "truck":
                return Truck.class;
            default:
                return null;
        }
    }
}

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

Fleet{vehicles=[Car{seatingCapacity=5, topSpeed=250.0, make='Mercedes-Benz', model='S500'}, Truck{payloadCapacity=7500.0, make='Isuzu', model='NQR'}]}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...