Java条件映射从一个对象到另一个对象?

huangapple go评论59阅读模式
英文:

Java conditional mapping from one object to another?

问题

建立一个允许客户端指定从内部领域对象投影到外部领域资源的 API。

> DB --> Foo 实体 --> Foo 映射器 --> Foo 资源

客户端发送一个名为 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());
    }
    
    // 等等。
    return resource;
}

有没有更简洁或更酷的方法来做到这一点,而不必拥有一个有着所有这些条件语句的 400 行函数?

另外,如果客户端发送的字段拼写或大小写不正确,解决方案应该忽略它,而不是抛出异常。

注意,我正在使用 Spring Boot 2.3 与 Spring Hateoas + Rest。

英文:

Looking to build an API that lets the client specify what fields they want projected from the internal Domain object to the external Domain resource

> DB --> Foo Entity --> Foo Mapper --> Foo Resource

Client sends a request parameter called fieldsToProject

e.g.

> fieldsToProject: ["id", "name", "description", "basePrice", "unitPrice", "manufacturer"]

I wrote a very crude method but it works like so

public FooResource toProjectedFooResource(Foo foo, List&lt;String&gt; fieldsToProject) {
    FooResource resource = new FooResource();

    if (fieldsToProject.contains(&quot;id&quot;)) {
        resource.setId(foo.getId());
    }

    if (fieldsToProject.contains(&quot;name&quot;)) {
        resource.setName(foo.getName());
    }

    if (fieldsToProject.contains(&quot;basePrice&quot;)) {
        resource.setBasePrice(foo.getBasePrice());
    }

    if (fieldsToProject.contains(&quot;unitPrice&quot;)) {
        resource.setUnitPrice(foo.getUnitPrice());
    }
    
    //etc.
    return resource;
}

Is there a neater or cooler way to do this without having a 400 line function with all these if statements?

Also if the client sends the fields with incorrect spelling or case then the solution should just ignore it, not throw an exception.

Note I am using Spring Boot 2.3 with Spring Hateoas + Rest

答案1

得分: 2

使用反射确实可以创建更紧凑的代码。我的方法会类似这样:

public FooResource toProjectedFooResource(Foo foo, List<String> fieldsToProject) {
    FooResource fr = new FooResource();
    for (Field field : foo.getClass().getDeclaredFields()) {
        if (fieldsToProject.contains(field.getName())) {
            try {
                // 注意,为了简单起见,使用属性描述符来获取方法,而不是自己构造getter和setter方法的名称
                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;
}

我会使用HashSet来代替List来存储fieldsToProject,这样性能会更好。

英文:

Using reflection you can create a more compact code indeed. My approach wold be something like this:

    public FooResource toProjectedFooResource(Foo foo, List&lt;String&gt; 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;
	}

I'd use a HashSet for the fieldsToProject instead a list, it will perform better.

答案2

得分: 1

这可以使用反射来实现。我为您制作了一些简单的示例,以便您了解其工作原理:

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 {
                //字段需要是公共的才能起作用
                Field inputField = input.getClass().getField(field);
                Field outputField = result.getClass().getField(field);
                outputField.set(result, inputField.get(input));
            } catch (Exception e) {
                e.printStackTrace();
            }

            try {
                //TODO:在此处放置一些将字段更改为驼峰命名的函数
                String fieldCamelCase = "Test1";
                Method inputGetMethod = Arrays.stream(input.getClass().getMethods())
                                       .filter(x -> x.getName().equals("get" + fieldCamelCase))
                                       .findFirst().orElse(null);

                Method outputSetMethod = Arrays.stream(input.getClass().getMethods())
                                       .filter(x -> x.getName().equals("set" + fieldCamelCase))
                                       .findFirst().orElse(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;
    }
}

它可能无法覆盖所有情况,但可以作为一个起点。

英文:

You can use reflection for this. I did make some simple example for you to see how it works:

public class Main {
public static void main(String[] args) {
List&lt;String&gt; fieldsToProject = Arrays.asList(&quot;test1&quot;);
Test input = new Test();
input.setTest1(&quot;1234&quot;);
input.setTest2(&quot;5678&quot;);
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 = &quot;Test1&quot;;
Method inputGetMethod = Arrays.stream(input.getClass().getMethods())
.filter(x -&gt; x.getName().equals(&quot;get&quot; + fieldCamelCase))
.findFirst().orElseGet(null);
Method outputSetMethod = Arrays.stream(input.getClass().getMethods())
.filter(x -&gt; x.getName().equals(&quot;set&quot; + 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;
}
}

it won't cover every case but it's a place to start.

huangapple
  • 本文由 发表于 2020年9月9日 20:59:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/63812252.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定