Java 从一个对象到另一个对象的条件映射?
Java conditional mapping from one object to another?
希望构建一个 API 让客户端指定他们想要从内部域对象投影到外部域资源的字段
DB --> Foo Entity --> Foo Mapper --> Foo Resource
客户端发送一个请求参数叫fieldsToProject
例如
fieldsToProject: ["id", "name", "description", "basePrice", "unitPrice", "manufacturer"]
我写了一个非常粗糙的方法,但它是这样工作的
public FooResource toProjectedFooResource(Foo foo, List<String> fieldsToProject) {
FooResource resource = new FooResource();
if (fieldsToProject.contains("id")) {
resource.setId(foo.getId());
}
if (fieldsToProject.contains("name")) {
resource.setName(foo.getName());
}
if (fieldsToProject.contains("basePrice")) {
resource.setBasePrice(foo.getBasePrice());
}
if (fieldsToProject.contains("unitPrice")) {
resource.setUnitPrice(foo.getUnitPrice());
}
//etc.
return resource;
}
有没有更简洁或更酷的方法来执行此操作而无需使用所有这些 if 语句的 400 行函数?
此外,如果客户端发送的字段拼写或大小写不正确,那么解决方案应该忽略它,而不是抛出异常。
注意我正在使用 Spring Boot 2.3 和 Spring Hateoas + Rest
您可以为此使用反射。我做了一些简单的例子让你看看它是如何工作的:
public class Main {
public static void main(String[] args) {
List<String> fieldsToProject = Arrays.asList("test1");
Test input = new Test();
input.setTest1("1234");
input.setTest2("5678");
Test result = new Test();
for (String field : fieldsToProject) {
try {
//Fields need to be public for this to work
Field inputField = input.getClass().getField(field);
Field outputField = result.getClass().getField(field);
outputField.set(inputField.get(input), result);
} catch (Exception e) {
e.printStackTrace();
}
try {
//TODO: Place here some function to change field to camel case
String fieldCamelCase = "Test1";
Method inputGetMethod = Arrays.stream(input.getClass().getMethods())
.filter(x -> x.getName().equals("get" + fieldCamelCase))
.findFirst().orElseGet(null);
Method outputSetMethod = Arrays.stream(input.getClass().getMethods())
.filter(x -> x.getName().equals("set" + fieldCamelCase))
.findFirst().orElseGet(null);
Object value = inputGetMethod.invoke(input);
outputSetMethod.invoke(result, value);
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(result.getTest1());
System.out.println(result.getTest2());
}
}
public class Test {
private String test1;
private String test2;
public String getTest1() {
return test1;
}
public Test setTest1(String test1) {
this.test1 = test1;
return this;
}
public String getTest2() {
return test2;
}
public Test setTest2(String test2) {
this.test2 = test2;
return this;
}
}
它不会涵盖所有情况,但它是一个起点。
使用反射确实可以创建更紧凑的代码。我的方法是这样的:
public FooResource toProjectedFooResource(Foo foo, List<String> fieldsToProject) {
FooResource fr = new FooResource();
for (Field field : foo.getClass().getDeclaredFields()) {
if (fieldsToProject.contains(field.getName())) {
try {
// Notice the use property descriptor for simplicity instead of constructing the getter setter method name by ourselves
new PropertyDescriptor(field.getName(), FooResource.class).getWriteMethod().invoke(fr,
new PropertyDescriptor(field.getName(), Foo.class).getReadMethod().invoke(foo, (Object[]) null));
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| IntrospectionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return fr;
}
我会为 fieldsToProject 使用 HashSet 而不是列表,它的性能会更好。
希望构建一个 API 让客户端指定他们想要从内部域对象投影到外部域资源的字段
DB --> Foo Entity --> Foo Mapper --> Foo Resource
客户端发送一个请求参数叫fieldsToProject
例如
fieldsToProject: ["id", "name", "description", "basePrice", "unitPrice", "manufacturer"]
我写了一个非常粗糙的方法,但它是这样工作的
public FooResource toProjectedFooResource(Foo foo, List<String> fieldsToProject) {
FooResource resource = new FooResource();
if (fieldsToProject.contains("id")) {
resource.setId(foo.getId());
}
if (fieldsToProject.contains("name")) {
resource.setName(foo.getName());
}
if (fieldsToProject.contains("basePrice")) {
resource.setBasePrice(foo.getBasePrice());
}
if (fieldsToProject.contains("unitPrice")) {
resource.setUnitPrice(foo.getUnitPrice());
}
//etc.
return resource;
}
有没有更简洁或更酷的方法来执行此操作而无需使用所有这些 if 语句的 400 行函数?
此外,如果客户端发送的字段拼写或大小写不正确,那么解决方案应该忽略它,而不是抛出异常。
注意我正在使用 Spring Boot 2.3 和 Spring Hateoas + Rest
您可以为此使用反射。我做了一些简单的例子让你看看它是如何工作的:
public class Main {
public static void main(String[] args) {
List<String> fieldsToProject = Arrays.asList("test1");
Test input = new Test();
input.setTest1("1234");
input.setTest2("5678");
Test result = new Test();
for (String field : fieldsToProject) {
try {
//Fields need to be public for this to work
Field inputField = input.getClass().getField(field);
Field outputField = result.getClass().getField(field);
outputField.set(inputField.get(input), result);
} catch (Exception e) {
e.printStackTrace();
}
try {
//TODO: Place here some function to change field to camel case
String fieldCamelCase = "Test1";
Method inputGetMethod = Arrays.stream(input.getClass().getMethods())
.filter(x -> x.getName().equals("get" + fieldCamelCase))
.findFirst().orElseGet(null);
Method outputSetMethod = Arrays.stream(input.getClass().getMethods())
.filter(x -> x.getName().equals("set" + fieldCamelCase))
.findFirst().orElseGet(null);
Object value = inputGetMethod.invoke(input);
outputSetMethod.invoke(result, value);
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(result.getTest1());
System.out.println(result.getTest2());
}
}
public class Test {
private String test1;
private String test2;
public String getTest1() {
return test1;
}
public Test setTest1(String test1) {
this.test1 = test1;
return this;
}
public String getTest2() {
return test2;
}
public Test setTest2(String test2) {
this.test2 = test2;
return this;
}
}
它不会涵盖所有情况,但它是一个起点。
使用反射确实可以创建更紧凑的代码。我的方法是这样的:
public FooResource toProjectedFooResource(Foo foo, List<String> fieldsToProject) {
FooResource fr = new FooResource();
for (Field field : foo.getClass().getDeclaredFields()) {
if (fieldsToProject.contains(field.getName())) {
try {
// Notice the use property descriptor for simplicity instead of constructing the getter setter method name by ourselves
new PropertyDescriptor(field.getName(), FooResource.class).getWriteMethod().invoke(fr,
new PropertyDescriptor(field.getName(), Foo.class).getReadMethod().invoke(foo, (Object[]) null));
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| IntrospectionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return fr;
}
我会为 fieldsToProject 使用 HashSet 而不是列表,它的性能会更好。