2015-07-01 11 views
14

Es gibt einen Spring-Ansatz zum Filtern von Feldern aus einer Serviceantwort mit JSON views, aber es fehlt ein äquivalenter Ansatz, um eine Antwort mit einigen dynamischen/synthetischen Feldern wie dieser anzureichern;Dynamische Felder zur Spring JSON-Ansichtsantwort hinzufügen

Ich könnte den Benutzer in der Ansicht umbrechen, aber ich möchte nicht alle erforderlichen Benutzerfelder manuell weitergeben.

Meine andere Idee war, die Ansichten mit dem Benutzerobjekt zu erweitern und eine Art Verweislinker zu erstellen, um Wertereferenzen vom Benutzerobjekt in die Ansicht zu kopieren, aber dies wird mit Sammlungen kompliziert.

Gibt es einen anderen Ansatz oder Rahmen, um dies zu erreichen? Wird dieses Konzept überhaupt nicht angesprochen?

Update:

Klärung durch Beispiel:

  • Ich will nicht das User-Objekt wickeln, weil ich nicht gleiche Getter-Methoden von User-Klasse in verschiedenen Userview Objekten halten will .
  • Ich kann den Benutzer nicht erweitern, weil es ein Domänenobjekt von anderen Ressourcen geladen ist.
  • Es sollte keine Referenz auf verschiedene UserView-Objekte im Benutzerobjekt geben.

Ich bin auf der Suche nach einer Art Fassadenlösung/Framework/Ansatz.

+0

So 'UserViewA 'kann nicht von' User' erben? In diesem Fall würde ich sagen, AOP ist Ihre beste Wette. – kaqqao

+0

@kaqqao 'UserViewA' kann von' User' erben, aber ich kann 'User' nicht erweitern/modifizieren. – kromit

Antwort

9

Wie wäre es jackson @JsonUnwrapped mit?

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 wird nur alle Eigenschaften des Benutzers auf die Root-Ebene zieht und hat immer noch die eigenen Eigenschaften von UserViewA dort.

+1

Das ist es! dauerte nur 18 Monate :) – kromit

-1

Wie geht das?

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

-Update auf Ihrem Kommentar basiert:

Ein Ansatz, den ich vorschlagen würde, ist eine Userview-Klasse zu erstellen, die Erweiterung oder die Zusammensetzung verwenden (ich in diesem Fall Erweiterung denken wäre ok). Für jede weitere Methode (getFullName) können Sie die spezifische Ansichtsstrategie basierend auf Ihren Anforderungen manuell anwenden.

Wenn Sie keine anspruchsvollen Anforderungen haben, können Sie alle zusätzlichen Methoden in der UserView hinzufügen (getFullNameA, getFullNameB) und sie mit verschiedenen JsonViews annotieren.

Wenn Sie eine Methode (getFullName) anstelle von zwei mit unterschiedlichen Namen haben, dann können Sie ein getFullName haben die Arrays.asList(getFullNameA(), getFullNameB()).get(0)) zurückkehrt und mit Anmerkungen versehen, die getFullNameA() und getFullNameB() mit unterschiedlichen Json Ansicht Anmerkungen. Auf diese Weise wird Ihre Liste immer einen Eintrag haben, abhängig von der verwendeten Ansicht.

+0

Dies funktioniert nur für ViewA, nicht für ViewB. Außerdem möchte ich mein Domain-Objekt nicht mit sichtspezifischer Logik aufblähen. Edit: Bei dieser Frage geht es mehr um ein Konzept, nicht um diesen speziellen Anwendungsfall. – kromit

+0

Basierend auf Ihrer Bearbeitung: Danke, aber 1. Wrapping der Benutzer verursacht zusätzliche Wartung. 2. Ich kann ein Domänenobjekt nicht erweitern. – kromit

+0

Sie können die zusätzliche Ansicht Strategien anwenden, indem AspectJ und ITD mit – Chris

4

Wenn Sie die Klasse des Domänenobjekts nicht ändern können, können Sie Ihren JSON mit "virtuellen" Feldern mit einem Mix-In anreichern.

Zum Beispiel könnten Sie eine Klasse erstellen namens UserMixin, die die firstName und lastName Felder versteckt und macht ein virtuelles fullName Feld:

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(); 
} 

Dann würden Sie eine Klasse implementieren UserFullName benannt, VirtualBeanPropertyWriter erstreckt sich die zur Verfügung zu stellen Wert des virtuellen Felds:

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); 
    } 
} 

Schließlich müssen Sie Ihren Mix mit dem ObjectMapper registrieren wie in der folgenden JUnit-Test gezeigt:

@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"))); 
} 

Der Ausgang ist dann:

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

Das Wrapper-Objekt (wenn Ihr Serialisierungsframework die Getter-Methode verwendet) oder das gesamte Objekt in ein anderes View-Objekt umschreiben (wenn Ihr Serialisierungsframework die Eigenschaft verwendet), ist der beste Weg. Wenn Sie Ihr Objekt nicht manuell umschreiben möchten, können Sie Framework wie dozer oder andere Java Bean mapping framework verwenden.

Wenn Ihr Modell wie folgt aussehen:

public class User { 
    private final String firstName; 
    private final String lastName; 

    // constructor and getter method 
} 

Ihre Wrap-Klasse könnte wie folgt aussehen:

public class UserView { 
    private final User user; 

    public UserView(User user) { 
     this.user = user; 
    } 

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

Oder können Sie auch ganze Objekt umschreiben:

public class UserView { 
    private final String fullName; 

    public UserView(User user) { 
     this.fullName = user.getFirstName() + " " + user.getLastName(); 
    } 

    // getter if needed 
} 
Verwandte Themen