英文:
Java generics: Map nested json response to Java objects
问题
情境:
我正在与一个不寻常的外部 API 进行交互,其中每个属性都是一个包含多个值的映射。
为了将这个响应转换为简单的 Java 对象,我不得不进行一些不太规范的解包操作。以下是典型的 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"));
}
}
这是我创建的示例实用方法 unbox
,以避免编写这样丑陋的代码。目前,它仅适用于返回 String 的情况。
import java.util.Map;
public class BaseUtil {
// TODO: 让这个方法变成泛型
public static String unbox(Map<String, String> data, String key) {
if (data != null && data.containsKey(key)) {
return data.get(key);
}
return null;
}
}
我正试图将上述方法转换为通用方法,可以动态指定返回类型并相应地进行数据转换。
有人可以帮我创建一个吗?
我尝试过这个:
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;
}
但显然这不起作用,但从理论上讲,这是我期望的解决方案。
编辑:这是一个复杂类型的示例输入:
// associatePartyRole 是一个字符串列表。
@JsonProperty(value = "associatedPartyRole")
public void unboxAssociatedPartyRole(Map<String, Object> data) {
this.associatedPartyRole = unbox(data, "value", List.class);
// 编译错误:需要列表,提供了对象。
}
编辑 2:这是最终解决方案:
PartyDetailsDto.java
```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"));
}
}
BaseUtil.java
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);
}
}
感谢 @deduper 和 @davidxxx 提供的答案。
<details>
<summary>英文:</summary>
Scenario:
I'm working with an unusual external API in which every attribute is a map with multiple values.
In order to convert this response into simple Java objects, I had to do some dirty unboxing. Below is one of the typical java class. As you can see how I'm unboxing data from response and mapping them to my java class:
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"));
}
}
This is the sample util method - `unbox` - which I've created in order to avoid writing such ugly code. Right now, it only works for cases where String is returned.
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;
}
}
I'm trying to convert above method into a generic one where I could specify the return type dynamically and cast the returned data accordingly.
Can anyone help me out in creating one?
I've tried this:
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;
}
But it obviously doesn't work but in theory that's the kind of solution that I'm expecting.
EDIT: Here's a sample input of complex type:
// 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.
}
**EDIT 2: Here's the final solution:**
**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"));
}
}
**BaseUtil.java**
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);
}
}
Thanks @deduper @davidxxx for your answers.
</details>
# 答案1
**得分**: 8
也许这样:
```java
public static <T> T unbox(Map<String, T> data, String key) {
if (data != null && data.containsKey(key)) {
return data.get(key);
}
return null;
}
在这里,T
表示 T extends Object
。
你可以用它来处理任何类:
Map<String, Integer> map = ...;
Integer value = unbox(map, "key");
注意,你可以简化你的实现,例如:
public static <T> T unbox(Map<String, T> data, String key) {
return Optional.ofNullable(data)
.map(m -> m.get(key))
.orElse(null);
}
这也更高效(只有一次映射访问)。
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));
}
它的输出是:
100
[1, 2]
[1, 2, 3]
如果这不是你要寻找的内容,请重新说明你想要做什么。
在需求变更后的编辑:
public void unboxIndividualDetails(Map<String, Object> individual) {...}
实际上,在这里你想要进行不安全的类型转换。为了实现这一点,你不需要传递一个 Class
实例,也不会使你的代码更加类型安全。你想要告诉编译器,接受以 Object
声明的对象可以分配给一个更具体类型的变量。从转换逻辑来看,这类似于:
Object o = Integer.valueOf(100);
Integer i = (Integer)o;
通过声明参数化的泛型类型方法,编译器从目标类型(接收方法调用返回的变量类型)中推断出 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);
}
这里是一个示例测试:
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);
}
输出:
1
hello
英文:
Maybe that :
public static <T> T unbox(Map<String, T> data, String key) {
if (data != null && data.containsKey(key)) {
return data.get(key);
}
return null;
}
Here T
implies T extends Object
.
That you can use so with any class:
Map<String, Integer> map = ...;
Integer value = unbox(map, "key");
Note that you could simplify your implementation such as :
public static <T> T unbox(Map<String, T> data, String key) {
return Optional.ofNullable(data)
.map(m -> m.get(key))
.orElse(null);
}
It is also more efficient (a single map access)
OP comment :
> I followed your solution but it doesn't seem to work when the return
> type is supposed to be a list or an array. How would i handle that
> case
That is surprising. It should work. Try that sample code :
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));
}
It outputs :
> 100
>
> [1, 2]
>
> [1, 2, 3]
If that is not what you are looking for. Please rewrite your question by specifying exactly what you want to perform.
Edit after requiement change :
public void unboxIndividualDetails(Map<String, Object> individual) {...}
In fact here you want to perform unsafe casts. To achieve that you don't need to pass a Class
instance and that not will not make your code more safe type either.
What you want is telling to the compiler to accept that the object that is declared as Object
be assigned to a more specific type variable.
In terms of cast logic that looks like :
Object o = Integer.valueOf(100);
Integer i = (Integer)o;
By declaring a parameterized generic type method, the compiler infers T
from the target type (the variable type that received the call method return), so you can do just return (T)myObject
.
Concrete code :
public static <T> T unboxUnsafe(Map<String, Object> data, String key) {
return Optional.ofNullable(data)
.map(m -> (T)m.get(key))
.orElse(null);
}
And here a sample test :
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);
}
Output :
> 1
> hello
答案2
得分: 2
> „…我跟随[@davidxx]的解决方案,但当返回类型应为列表或数组时,似乎不起作用。我该如何处理这种情况?…“
通过我称之为„EDD“(实验驱动开发)的过程,以下处理这些情况的方法出现了…
public static < T > T unbox( Map< String, T > data, String key, Class< ? > ofType ) {
if ( data != null && data.containsKey( key ) ) {
return (T)ofType.cast( data.get( key ) ) ;
}
return null;
}
您可以在main(String[])
中观察到以下调用成功返回了预期结果…
...
List< String > unBoxedList = unbox( mapOfLists, foo, List.class );
...
List< ? >[ ] unBoxedArrayOfLists = unbox( mapOfArrayOfLists, "bar", List[ ].class );
...
String unBoxedString = unbox( mapOfStrings, foo, String.class );
...
Integer unBoxedInteger = unbox( mapOfIntegers, bar, Integer.class );
...
点击上面链接中的绿色开始按钮,运行实验。
在来自@saran3h的评论中获得反馈后,澄清了他的用例,从实验的后续迭代中出现了以下重构…
public class BaseUtil {
public List<Object> associatedPartyRole ;
// TODO: 使这个方法泛化
public static < T > T unbox( Map< String, T > data, String key, Class< ? > ofType ) {
if ( data != null && data.containsKey( key ) ) {
return (T)ofType.cast( data.get( key ) ) ;
}
return null;
}
public void unboxAssociatedPartyRole(Map<String, Object> data) {
this.associatedPartyRole = (List)unbox(data, "foo", Object.class);
}
}
这个新情况已经成功测试过了…
...
private static final Map<String, Object> mapOfObjects = new HashMap< >( );
...
mapOfObjects.put( foo, (Object)mapOfLists.get( foo ) );
...
BaseUtil user = new BaseUtil( );
user.unboxAssociatedPartyRole( mapOfObjects );
List< Object > objs = user.associatedPartyRole;
assertIsA( objs, List.class );
观察上述重构运行实验的结果 (原谅我的法语)…
[What The Reifiable Fuck@&*%$*!?]
实验成功
英文:
> „…I followed [@davidxx's] solution but it doesn't seem to work when the return type is supposed to be a list or an array. How would i handle that case?…“
Through a process I call „EDD“ („Experiment-driven Development“) the following way to handle those cases emerged…
public static < T > T unbox( Map< String, T > data, String key, Class< ? > ofType ) {
if ( data != null && data.containsKey( key ) ) {
return (T)ofType.cast( data.get( key ) ) ;
}
return null;
}
You can observe in main(String[])
that the following calls successfully return the expected result…
...
List< String > unBoxedList = unbox( mapOfLists, foo, List.class );
...
List< ? >[ ] unBoxedArrayOfLists = unbox( mapOfArrayOfLists, "bar", List[ ].class );
...
String unBoxedString = unbox( mapOfStrings, foo, String.class );
...
Integer unBoxedInteger = unbox( mapOfIntegers, bar, Integer.class );
...
Click the green Start button at the top of the page in the link above, to run the experiment.
After feedback in the comments from @saran3h that clarified his use case, the following refactor emerged from a subsequent iteration of the experiment…
public class BaseUtil {
public List<Object> associatedPartyRole ;
// TODO: Make this method generic
public static < T > T unbox( Map< String, T > data, String key, Class< ? > ofType ) {
if ( data != null && data.containsKey( key ) ) {
return (T)ofType.cast( data.get( key ) ) ;
}
return null;
}
public void unboxAssociatedPartyRole(Map<String, Object> data) {
this.associatedPartyRole = (List)unbox(data, "foo", Object.class);
}
}
That new case was successfully tested with…
...
private static final Map<String, Object> mapOfObjects = new HashMap< >( );
...
mapOfObjects.put( foo, (Object)mapOfLists.get( foo ) );
...
BaseUtil user = new BaseUtil( );
user.unboxAssociatedPartyRole( mapOfObjects );
List< Object > objs = user.associatedPartyRole;
assertIsA( objs, List.class );
Observe the results of running the experiment with the above refactor (pardon my French)…
[What The Reifiable Fuck@&*%$*!?]
EXPERIMENT SUCCESSFUL
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论