【问题标题】:Add dynamic fields to Spring JSON view response将动态字段添加到 Spring JSON 视图响应
【发布时间】:2015-09-18 07:29:14
【问题描述】:

有一种 Spring 方法可以使用 JSON views 从服务响应中过滤掉字段,但我缺少一种等效的方法来丰富响应,其中包含像这样的一些动态/合成字段;

class User{
    getFirstName(){...}
    getLastName(){...}
    getCreateDate(){...}
}

class UserViewA{
    getFullName(){
      return getLastName()+", "+getFirstName()
    }
    getCreateDate(){...}
}

class UserViewB{
    getFullName(){
      return getFirstName()+" "+getLastName()
    }
    getCreateDate(){...}
}

我可以将用户包装在视图中,但我不想手动传播所有需要的用户字段。

我的另一个想法是使用用户对象扩展视图并创建某种引用链接器以将值引用从用户对象复制到视图,但是这对于集合来说会变得复杂。

是否有其他方法或框架来实现这一点?这个概念根本没有解决吗?

更新:

举例说明:

  • 我不想包装 User 对象,因为我不想在不同的 UserView 对象中维护来自 User 类的相同 getter 方法。
  • 我无法扩展用户,因为它是从其他资源加载的域对象。
  • 不应在 User 对象中引用不同的 UserView 对象。

我正在寻找一种外观解决方案/框架/方法。

【问题讨论】:

  • 所以UserViewA不能继承User?在这种情况下,我会说 AOP 是你最好的选择。
  • @kaqqao UserViewA 可以从User 继承,但我不能扩展/修改User

标签: java json spring jackson


【解决方案1】:

用杰克逊@JsonUnwrapped怎么样?

http://fasterxml.github.io/jackson-annotations/javadoc/2.0.0/com/fasterxml/jackson/annotation/JsonUnwrapped.html

public class UserViewA {

    @JsonUnwrapped
    private User user;

    public User getUser() ...

    public String getFullName() {
        return user.getFirstName() + " " + user.getLastName()
    }
}

JsonUnwrapped 只会将 User 的所有属性拉到根级别,并且其中仍然有 UserViewA 自己的属性。

【讨论】:

  • 就是这样!只用了 18 个月 :)
【解决方案2】:

当您无法修改域对象的类时,您可以通过混合使用“虚拟”字段来丰富您的 JSON。

例如,您可以创建一个名为 UserMixin 的类,该类隐藏 firstNamelastName 字段并公开一个虚拟的 fullName 字段:

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonAppend;

import java.util.Date;

@JsonAppend(
    prepend = true,
    props = {
            @JsonAppend.Prop(name = "fullName", value = UserFullName.class)
    })
public abstract class UserMixin
{
    @JsonIgnore
    public abstract String getFirstName();
    @JsonIgnore
    public abstract String getLastName();
    public abstract Date getCreatedDate();
}

然后您将实现一个名为 UserFullName 的类,该类扩展 VirtualBeanPropertyWriter 以提供虚拟字段的值:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedClass;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.ser.VirtualBeanPropertyWriter;
import com.fasterxml.jackson.databind.util.Annotations;

public class UserFullName extends VirtualBeanPropertyWriter
{
    public UserFullName() {}

    public UserFullName(BeanPropertyDefinition propDef, Annotations contextAnnotations, JavaType declaredType)
    {
        super(propDef, contextAnnotations, declaredType);
    }

    @Override
    protected Object value(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception
    {
        return ((User) bean).getFirstName() + " " + ((User) bean).getLastName();
    }

    @Override
    public VirtualBeanPropertyWriter withConfig(MapperConfig<?> config, AnnotatedClass declaringClass, BeanPropertyDefinition propDef, JavaType type)
    {
        return new UserFullName(propDef, null, type);
    }
}

最后,您需要使用 ObjectMapper 注册您的混入,如以下 JUnit 测试所示:

@Test
public void testUserFullName() throws IOException
{
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.addMixIn(User.class, UserMixin.class);
    System.out.println(objectMapper.writeValueAsString(new User("Frodo", "Baggins")));
}

那么输出是:

{"fullName":"Frodo Baggins","createdDate":1485036953535}

【讨论】:

    【解决方案3】:

    实际上,最好的方法是包装 User 对象(如果您的序列化框架使用 getter 方法)或将整个对象重写为另一个视图对象(如果您的序列化框架使用属性)。如果您不想手动重写对象,可以使用 dozer 或其他 Java Bean mapping framework 之类的框架。

    如果您的模型如下所示:

    public class User {
        private final String firstName;
        private final String lastName;
    
        // constructor and getter method
    }
    

    您的包装类可能如下所示:

    public class UserView {
        private final User user;
    
        public UserView(User user) {
            this.user = user;
        }
    
        public String getFullName() {
            return user.getFirstName() + " " + user.getLastName();
        }
    }
    

    或者你也可以重写整个对象:

    public class UserView {
        private final String fullName;
    
        public UserView(User user) {
            this.fullName = user.getFirstName() + " " + user.getLastName();
        }
    
        // getter if needed
    }
    

    【讨论】:

      【解决方案4】:

      这个怎么样?

      @Transient
      @JsonProperty("fullName")
      getFullName(){
        return getLastName()+", "+getFirstName()
      }
      

      根据您的评论更新:

      我建议的一种方法是创建一个使用扩展或组合的 UserView 类(我认为在这种情况下扩展是可以的)。在每个附加方法 (getFullName) 上,您可以根据需要手动应用特定的视图策略。

      如果您没有复杂的需求,您可以在 UserView 中添加所有额外的方法(getFullNameA、getFullNameB)并使用不同的 JsonView 对其进行注释。

      如果您需要一个方法 (getFullName) 而不是两个具有不同名称的方法,您可以有一个返回 Arrays.asList(getFullNameA(), getFullNameB()).get(0)) 的 getFullName 并使用不同的 Json View 注释来注释 getFullNameA() 和 getFullNameB()。这样,您的列表将始终只有一项,具体取决于所使用的视图。

      【讨论】:

      • 这仅适用于 ViewA,不适用于 ViewB。此外,我不想用视图特定的逻辑来膨胀我的域对象。编辑:这个问题更多的是关于一个概念,而不是这个特定的用例。
      • 根据您的编辑:谢谢,但是 1. 包装用户会导致额外的维护。 2. 我无法扩展域对象。
      • 您可以使用 AspectJ 和 ITD 应用额外的视图策略
      • 通过 ITD 向域对象添加 json 视图功能的另一种方法是使用组合创建 UserView(声明私有 User 实例)并使用 lombok 将 User 方法委托给 User 类。这样您就不必使用 User 方法污染您的 UserView。见projectlombok.org/features/Delegate.html
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-07
      • 2011-09-07
      • 1970-01-01
      相关资源
      最近更新 更多