Добавить динамические поля в Spring ответ JSON
Существует подход Spring для фильтрации полей из ответа сервиса с представлениями JSON, но мне не хватает эквивалентного подхода для обогащения ответа некоторыми динамическими/синтаксическими полями, подобными этому;
class User{
getFirstName(){...}
getLastName(){...}
getCreateDate(){...}
}
class UserViewA{
getFullName(){
return getLastName()+", "+getFirstName()
}
getCreateDate(){...}
}
class UserViewB{
getFullName(){
return getFirstName()+" "+getLastName()
}
getCreateDate(){...}
}
Я мог бы обернуть пользователя в представление, но я не хочу распространять все необходимые пользовательские поля вручную.
Моя другая идея состояла в том, чтобы расширить представления пользовательским объектом и создать своего рода ссылочный компоновщик для копирования ссылок на значения из пользовательского объекта в представление, но это усложняется с коллекциями.
Есть ли какой-то другой подход или рамки для достижения этой цели? Разве эта концепция вообще не рассматривается?
Обновить:
Разъяснение по примеру:
- Я не хочу оборачивать объект User, потому что я не хочу поддерживать одни и те же методы получения из класса User в разных объектах UserView.
- Я не могу продлить пользователя, потому что это объект домена, загруженный из другого ресурса.
- В объекте User не должно быть ссылок на разные объекты UserView.
Я ищу своеобразное фасадное решение/каркас/подход.
Ответы
Ответ 1
Как насчет использования jackson @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 просто вытащит все свойства пользователя на корневой уровень и все еще будет иметь свои собственные свойства UserViewA.
Ответ 2
Если вы не можете изменить класс объекта домена, вы можете обогатить JSON "виртуальными" полями с помощью микширования.
Например, вы можете создать класс с именем UserMixin
, который скрывает поля firstName
и lastName
и предоставляет виртуальное поле 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);
}
}
Наконец, вам нужно будет зарегистрировать свой mix-in с 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 структура отображения.
Если ваша модель выглядит так:
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) вместо двух с другим именем, вы можете получить getFullName, которое возвращает Arrays.asList(getFullNameA(), getFullNameB()).get(0))
и аннотирует getFullNameA() и getFullNameB() с различными аннотациями Json View. Таким образом, ваш список будет иметь всегда один элемент, в зависимости от используемого вида.