Java 泛型:将嵌套的 json 响应映射到 Java 对象

sar*_*n3h 1 java generics

场景:我正在使用一个不寻常的外部 API,其中每个属性都是一个具有多个值的映射。为了将此响应转换为简单的 Java 对象,我不得不进行一些脏拆箱。下面是典型的java类之一。正如您所看到的,我是如何从响应中拆箱数据并将它们映射到我的 Java 类的:

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;

import java.time.LocalDate;
import java.util.Map;

import static com.my.util.BaseUtil.unbox;

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PartyDetailsDto {

    private String partyId;
    private String partyType;
    private String title;
    private String firstName;
    private String lastName;
    private String middleName;
    private LocalDate dateOfBirth;

    @JsonProperty(value = "partyId")
    public void unboxPartyId(Map<String, String> data) {
        this.partyId = unbox(data, "evidenceId");
    }

    @JsonProperty(value = "partyType")
    public void unboxPartyType(Map<String, String> partyType) {
        this.partyType = unbox(partyType, "value");
    }

    @JsonProperty(value = "individual")
    public void unboxIndividualDetails(Map<String, Object> individual) {
        Map<String, String> title = (Map<String, String>) individual.get("title");
        Map<String, String> firstName = (Map<String, String>) individual.get("firstName");
        Map<String, String> lastName = (Map<String, String>) individual.get("lastName");
        Map<String, String> middleName = (Map<String, String>) individual.get("middleName");
        Map<String, String> dateOfBirth = (Map<String, String>) individual.get("birthDate");

        this.title = unbox(title, "value");
        this.firstName = unbox(firstName, "value");
        this.lastName = unbox(lastName, "value");
        this.middleName = unbox(middleName, "value");
        this.dateOfBirth = LocalDate.parse(unbox(dateOfBirth, "value"));
    }
}
Run Code Online (Sandbox Code Playgroud)

这是示例 util 方法 -unbox我创建它是为了避免编写如此丑陋的代码。现在,它仅适用于返回 String 的情况。

import java.util.Map;

public class BaseUtil {
    // TODO: Make this method generic
    public static String unbox(Map<String, String> data, String key) {
        if (data != null && data.containsKey(key)) {
            return data.get(key);
        }
        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试将上述方法转换为通用方法,我可以在其中动态指定返回类型并相应地转换返回的数据。

谁能帮我创建一个?

我试过这个:

public static <T> T unbox(Map<String, String> data, String key, Class<T> type) {
        if (data != null && data.containsKey(key)) {
            return (type) data.get(key);
        }
        return null;
    }
Run Code Online (Sandbox Code Playgroud)

但这显然行不通,但理论上这是我所期待的解决方案。

编辑:这是复杂类型的示例输入:

// The associatePartyRole is a list of Stings.
@JsonProperty(value = "associatedPartyRole")
public void unboxAssociatedPartyRole(Map<String, Object> data) {
    this.associatedPartyRole = unbox(data, "value", List.class); 

    // Compilation error: Need list, provided object.
}
Run Code Online (Sandbox Code Playgroud)

编辑 2:这是最终的解决方案: PartyDetailsDto.java

public class PartyDetailsDto implements Serializable {
    private static final long serialVersionUID = 3851075484507637508L;

    private String partyId;
    private String partyType;
    private String title;
    private String firstName;
    private String lastName;
    private String middleName;
    private LocalDate dateOfBirth;

    @JsonProperty(value = "partyId")
    public void unboxPartyId(Map<String, String> data) {
        this.partyId = unbox(data, "evidenceId");
    }

    @JsonProperty(value = "partyType")
    public void unboxPartyType(Map<String, String> partyType) {
        this.partyType = unbox(partyType, "value");
    }

    @JsonProperty(value = "individual")
    public void unboxIndividualDetails(Map<String, Object> individual) {
        this.title = unbox(unbox(individual, "title", Map.class), "value");
        this.firstName = unbox(unbox(individual, "firstName", Map.class), "value");
        this.lastName = unbox(unbox(individual, "lastName", Map.class), "value");
        this.middleName = unbox(unbox(individual, "middleName", Map.class), "value");
        this.dateOfBirth = LocalDate.parse(unbox(unbox(individual, "title", Map.class), "value"));
    }
}
Run Code Online (Sandbox Code Playgroud)

基础实用程序

public class BaseLineUtil {
    public static <T> T unbox(Map<String, Object> data, String key, Class<?> ofType) {
        return Optional.ofNullable(data)
                .map(m -> (T) ofType.cast(m.get(key)))
                .orElse(null);
    }

    public static <T> T unbox(Map<String, T> data, String key) {
        return Optional.ofNullable(data)
                .map(m -> (T) m.get(key))
                .orElse(null);
    }
}
Run Code Online (Sandbox Code Playgroud)

感谢@deduper @davidxxx 的回答。

dav*_*xxx 8

也许:

public static <T> T unbox(Map<String, T> data, String key) {
    if (data != null && data.containsKey(key)) {
        return data.get(key);
    }
    return null;
}
Run Code Online (Sandbox Code Playgroud)

这里T暗示T extends Object.

你可以在任何类中使用它:

Map<String, Integer> map = ...;
Integer value = unbox(map, "key");
Run Code Online (Sandbox Code Playgroud)

请注意,您可以简化您的实现,例如:

public static <T> T unbox(Map<String, T> data, String key) {
  return Optional.ofNullable(data)
                 .map(m -> m.get(key))
                 .orElse(null);
}
Run Code Online (Sandbox Code Playgroud)

也更高效(单图访问)

OP评论:

我遵循了您的解决方案,但是当返回类型应该是列表或数组时,它似乎不起作用。我将如何处理那个案子

这是令人惊讶的。它应该工作。试试那个示例代码:

  public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<>();
    map.put("key", 100);
    Integer value = unbox(map, "key");
    System.out.println(value);

    Map<String, List<Integer>> mapOfList = new HashMap<>();
    mapOfList.put("key", Arrays.asList(1, 2));
    List<Integer> valueList = unbox(mapOfList, "key");
    System.out.println(valueList);

    Map<String, int[]> mapOfArray = new HashMap<>();
    mapOfArray.put("key", new int[] { 1, 2, 3 });
    int[] valueArray = unbox(mapOfArray, "key");
    System.out.println(Arrays.toString(valueArray));
  }
Run Code Online (Sandbox Code Playgroud)

它输出:

100

[1, 2]

[1, 2, 3]

如果这不是你要找的。请通过准确指定您要执行的操作来重写您的问题。


需求更改后编辑:

public void unboxIndividualDetails(Map<String, Object> individual) {...}
Run Code Online (Sandbox Code Playgroud)

事实上,在这里你想执行不安全的转换。要实现这一点,您不需要传递Class实例,也不会使您的代码类型更安全。
您想要的是告诉编译器接受声明为Object分配给更具体类型变量的对象。
在转换逻辑方面,看起来像:

Object o = Integer.valueOf(100);
Integer i = (Integer)o;
Run Code Online (Sandbox Code Playgroud)

通过声明参数化泛型类型方法,编译器T从目标类型(接收调用方法返回的变量类型)推断,因此您可以只执行return (T)myObject.

具体代码:

public static <T> T unboxUnsafe(Map<String, Object> data, String key) {
    return Optional.ofNullable(data)
                   .map(m -> (T)m.get(key))
                   .orElse(null);
}
Run Code Online (Sandbox Code Playgroud)

这里有一个示例测试:

public static void main(String[] args) {
    Map<String, Object> mapOfObjects = new HashMap< >( );
    mapOfObjects.put("longKey", 1L );
    mapOfObjects.put("stringKey", "hello" );
    Long l = unboxUnsafe(mapOfObjects, "longKey");
    System.out.println(l);
    String s = unboxUnsafe(mapOfObjects, "stringKey");
    System.out.println(s);
}
Run Code Online (Sandbox Code Playgroud)

输出 :

1

你好