【问题标题】:Parsing YAML with arrays of different types用不同类型的数组解析 YAML
【发布时间】:2019-11-01 20:23:02
【问题描述】:

我正在尝试读取 YAML 文件并将结果存储在 POJO 列表中。

我无法修改 YAML 文件。我使用 Jackson 2.10.0,但我对任何其他版本持开放态度。我正在尝试用 Jackson 解析以下脚本:

vehicles-notype.yaml

Vehicles 基本上是一个对象列表,这些对象具有一些共同属性和一些特定于车辆类型的属性。

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

CarTruck 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 属性系列可以发挥作用,但我正在慢慢失去它;-)。

【问题讨论】:

    标签: java jackson yaml


    【解决方案1】:

    cartruck 是字段名、属性。我不知道Jackson 注释允许从不同字段设置类型。

    如果Yaml文件不能被修改,我们可以使用Streaming API读取类型属性并反序列化Vehicle。在伪代码中它可能看起来像:

    while token != EOF
        while token != FIELD_NAME
            nextToken()
    
        fieldName = nextFieldName();
        clazz = convertToClass(fieldName);
        vehicles.add(read(clazz));
    

    幸运的是,定义类型的字段名称是第一个字段名称,我们可以手动读取它,然后使用Jackson 读取类型。我从Vehicle 类中删除了JsonSubTypesJsonTypeInfo 注释,使用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'}]}
    

    【讨论】:

    • 这很棒 Micha... 但是,当我这样做时: ObjectMapper mapper = new ObjectMapper(new YAMLFactory());字符串 jsonDataString = mapper.writeValueAsString(fleet);然后它会丢失类型。你认为我需要一个自定义序列化程序吗?
    • @jgp,因为我们删除了与类型信息相关的所有注释,我们需要编写自定义序列化器或在每个类中提供额外的字段/getter:Car 类中的getCar(){return "";} 和@ 中的getTruck(){return "";} 987654342@上课。如果需要用 @JsonPropertyOrder 注释类,以确保该类型将是第一个字段。
    • Brilliant :)... 我仍然得到类似的东西: - car: "" make: "Mercedes-Benz" 我有机会摆脱双引号吗?我尝试了包含/空值,但没有运气......
    • @jgp 这不是一件容易的事,因为在幕后使用了SnakeYAML,您应该以某种方式将其配置为不为空字符串写引号。此外,这不是一个错误,而是一个功能:(yaml) Empty string serialized without quotes if MINIMIZE_QUOTES is enabled。请参阅文档中的 SnakeYaml dump function writes with single quotes8.1.2. Literal Style
    • @jgp,我发现了一个肮脏的 hack,但我不确定它是否适合你,因为我们禁用了所有 String 值的引号。您需要启用YAMLGenerator.Feature.MINIMIZE_QUOTES 功能。例如:factory.enable(YAMLGenerator.Feature.MINIMIZE_QUOTES); 而不是空字符串,您需要返回 Non-breaking space: public String getCar() {return "\u00A0";}public char getTruck() {return 0X00A0;} 。现在,此字符在输出中不可见,并且应删除引号。缺点:引号到处都被删除了。
    猜你喜欢
    • 1970-01-01
    • 2018-12-20
    • 1970-01-01
    • 2015-05-14
    • 1970-01-01
    • 2015-04-26
    • 1970-01-01
    • 2015-06-20
    • 1970-01-01
    相关资源
    最近更新 更多