更简洁的方法来迭代对象列表中的可选属性?

问题描述

我有一个项目对象列表

“Items”: [
      {
        "Identifier": {
          “Identity”: {
            “ID”: “123”,“Country” : “Japan”
          }
        },“Color”: “Red”,“Size”: {
          "Units": “cm”,"value": 140
        }
      },{
        "Identifier": {
          “Identity”: {
            “ID”: “345”,“Country” : “Russia”
          }
        },“Weight”: “90 lb”,“Height” : “170 cm”
      }]

项目类看起来像这样

public class Item {

//properties 
private IdentifierType Identifier = null;
private String Color = null;
private DimensionType Dimensions = null;
private String Weight = null;
private String Height = null;

With corresponding getter and setters for the above attributes

}

我正在尝试从项目列表中读取项目对象并创建
的映射 属性对象就像

Public class property {
     String propertyName
     String propertyValue
}

例如

{
   123,[ {“propertyName” : “Color”,“propertyValue”: “Red”},{“propertyName” : size,“propertyValue”: “140 cm”}],345,[ {“propertyName” : “Weight”,“propertyValue”: “90 lb”},{“propertyName” : Height,“propertyValue”: “170 cm”}]
}

我能够迭代列表以获取项目对象的 ID,但我无法在不检查每个属性是否为空的情况下创建属性对象列表。 为了填充属性列表,我必须对 item 对象中的所有属性执行 get 并为非空的属性创建属性对象。 (修改项目类不是一个选项)

我正在寻找一种更简洁的方法来做到这一点。

解决方法

让你的数据:

{
  "Items": [
    {
      "Identifier": {
        "Identity": {
          "ID": "123","Country": "Japan"
        }
      },"Color": "Red","Size": {
        "Units": "cm","value": 140
      }
    },{
      "Identifier": {
        "Identity": {
          "ID": "345","Country": "Russia"
        }
      },"Weight": "90 lb","Height": "170 cm"
    }
  ]
}

我建议首先创建一个适合您需求的合适的数据结构(例如):

static class Identity {
    public String ID;
    public String Country;
}

static class Identifier {
    public Identity Identity;
}

static class Size {
    public String Units;
    public Integer value;
}

static class Item {
    public Identifier Identifier;
    public String Color;
    public Size Size;
    public String Height;
    public String Weight;
}

static class Data {
    public List<Item> Items;
}

然后您可以使用一种稳健的方式从您的数据文件(流、字符串、...)中进行解析

Data data = new ObjectMapper().readValue(new File("test.json"),Data.class);

现在,您可以随心所欲地进行转换,以遍历所有属性并收集不可为空的内容,我们定义了 Property

static class Property {
    public String name;
    public String value;

    public Property(String name,String value) {
        this.name = name;
        this.value = value;
    }

    // make a property from a nullable value
    public static Optional<Property> from(String name,Object value) {
        return ofNullable(value).map(v -> new Property(name,v.toString()));
    }
}

from 方法是避免 if 的帮手。

要收集我们必须遍历所有属性的属性:

// flatMap to properties
List<Property> properties = data
        .Items.stream()                         // get items as stream
        .flatMap(item ->                        // flat map all their properties you want
                Stream.of(                      // collect all of them
                    ofNullable(item.Identifier)             // if not null
                        .map(id -> id.Identity)             // if not null
                        .map(id -> Stream.of(
                                from("ID",id.ID),from("Country",id.Country)
                        ))
                        .orElse(Stream.empty()),ofNullable(item.Size)                   // if not null
                            .map(size -> Stream.of(
                                    from("Units",size.Units),from("value",size.value)
                            ))
                            .orElse(Stream.empty()),Stream.of(from("Color",item.Color)),Stream.of(from("Height",item.Height)),Stream.of(from("Weight",item.Weight))
                )
                .flatMap(x -> x)                // flat map nested property lists
                .filter(Optional::isPresent)    // only presents
                .map(Optional::get)
        )
        .collect(toList());

带输出:

ID: 123
Country: Japan
Units: cm
value: 140
Color: Red
ID: 345
Country: Russia
Height: 170 cm
Weight: 90 lb

ID 不是必需的,但我们可以使用默认值进行分组:

Map<String,List<Property>> properties = data
        .Items.stream()
        .collect(groupingBy(
                item -> ofNullable(item.Identifier).map(id -> id.Identity).map(id -> id.ID).orElse("no-id"),collectingAndThen(toList(),xs -> xs
                .stream()
                ...(same aggregation)...

现在,打印地图

// print all
properties.forEach((id,values) -> {
    System.out.printf("== %s === %n",id);
    values.forEach(v -> System.out.printf("  %s: %s%n",v.name,v.value));
});

带输出

== 123 === 
  ID: 123
  Country: Japan
  Units: cm
  value: 140
  Color: Red
== 345 === 
  ID: 345
  Country: Russia
  Height: 170 cm
  Weight: 90 lb

你有一个特定的契约(你定义的类),虽然你应该写更多的代码行我推荐以前的解决方案(一个一个映射每个特定类型),你的解决方案会很多更健壮。

无论如何,如果您希望此解决方案适用于任何类层次结构,您可以使用反射,只需将所有先前的 flatMap lambda 替换为:

public static Stream<Property> from(Object object) {
    if (object == null)
        return Stream.empty();
    List<Stream<Property>> rs = new ArrayList<>();
    Class<?> clazz = object.getClass();
    for (Field field : clazz.getFields())
        if (field.getType() == String.class || field.getType() == Integer.class)
            rs.add(from(field.getName(),field.get(object)).map(Stream::of).orElse(Stream.empty()));
        else
            rs.add(from(field.get(object)));
    return rs.stream().flatMap(x -> x);
}

新替换的聚合是

collectingAndThen(toList(),xs -> xs
        .stream()
        .flatMap(Property::from)
        .collect(toList()))

但是您必须编写特殊情况,例如“最终数据”是什么意思IntegerString……还有什么?)和其他特殊情况(以及可能的非标准)结构,例如 List<>Stream<>、...,您将进入 Lombok、Jackson 的世界...

(使用反射的新输出是)

== 123 === 
  ID: 123
  Country: Japan
  Color: Red
  Units: cm
  value: 140
== 345 === 
  ID: 345
  Country: Russia
  Height: 170 cm
  Weight: 90 lb