2015-07-01 14 views
14

Esiste un approccio Spring per filtrare i campi da una risposta del servizio con JSON views, ma manca un approccio equivalente per arricchire una risposta con alcuni campi dinamici/sintetici come questo;Aggiungere campi dinamici a Spring Risposta JSON

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

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

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

Potrei racchiudere l'utente all'interno della vista, ma non voglio propagare manualmente tutti i campi utente necessari.

Un'altra mia idea era di estendere le viste con l'oggetto utente e creare una sorta di linker di riferimento per copiare i riferimenti dei valori dall'oggetto utente alla vista, ma questo si complicherebbe con le raccolte.

C'è qualche altro approccio o struttura per raggiungere questo obiettivo? Questo concetto non è affrontato in alcun modo?

Aggiornamento:

Chiarimento con l'esempio:

  • Non voglio per avvolgere l'oggetto utente perché non voglio mantenere stessi metodi getter dalla classe per l'utente in diversi oggetti UserView .
  • Non riesco ad estendere l'Utente perché è un oggetto dominio caricato da un'altra risorsa.
  • Non ci dovrebbero essere riferimenti a diversi oggetti UserView nell'oggetto Utente.

Sto cercando un tipo di soluzione/struttura/approccio facciata.

+0

Così 'UserViewA 'non può ereditare da' Utente'? In tal caso, direi che AOP è la soluzione migliore. – kaqqao

+0

@kaqqao 'UserViewA' può ereditare da' Utente' ma non posso estendere/modificare 'Utente'. – kromit

risposta

9

ne dite di usare 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 sarà solo tirare tutte le proprietà di utente al livello principale e avere ancora le proprie caratteristiche di UserViewA in là.

+1

Ecco fatto! ci sono voluti solo 18 mesi :) – kromit

-1

How'bout questo?

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

aggiornamento sulla base di un commento:

Un approccio io suggerirei è quello di creare una classe UserView che utilizzerà l'estensione o la composizione (penso che in questo caso l'estensione sarebbe ok). Su ogni metodo aggiuntivo (getFullName) è possibile applicare manualmente una specifica strategia di visualizzazione in base alle proprie esigenze.

Se non si dispone di requisiti sofisticati, è possibile aggiungere tutti i metodi aggiuntivi in ​​UserView (getFullNameA, getFullNameB) e annotarli con JsonViews diversi.

Se è necessario disporre di un metodo (getFullName) invece di due, con nome diverso, si può avere un getFullName che restituisce Arrays.asList(getFullNameA(), getFullNameB()).get(0)) e annotare il getFullNameA() e getFullNameB() con differenti JSON Vista annotazioni. In questo modo la tua lista avrà sempre un elemento, a seconda della vista utilizzata.

+0

Funzionerà solo per ViewA, non ViewB. Inoltre, non voglio gonfiare il mio oggetto dominio con la logica specifica della vista. Modifica: questa domanda riguarda più un concetto, non questo caso d'uso specifico. – kromit

+0

In base alla modifica: Grazie, ma 1. Il riavvolgimento dell'utente causa una manutenzione aggiuntiva. 2. Non riesco a estendere un oggetto dominio. – kromit

+0

È possibile applicare le strategie di visualizzazione aggiuntive utilizzando AspectJ e ITD – Chris

4

Quando non è possibile modificare la classe dell'oggetto dominio, è possibile arricchire il JSON con campi "virtuali" utilizzando un mix-in.

Ad esempio, si potrebbe creare una classe denominata UserMixin che nasconde i campi firstName e lastName e che espone un virtuale fullName campo:

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

allora si sarebbe implementare una classe denominata UserFullName che si estende VirtualBeanPropertyWriter per fornire il valore del campo virtuale:

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

Infine, è necessario registrare il proprio mix-in con ObjectMapper come mostrato nel seguente test 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"))); 
} 

Il risultato è quindi:

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

In realtà, il modo migliore per farlo è racchiudere l'oggetto Utente (se il framework di serializzazione utilizza il metodo getter) o riscrivere l'intero oggetto in un altro oggetto di visualizzazione (se la struttura di serializzazione utilizza la proprietà). Se non si desidera riscrivere manualmente l'oggetto, è possibile utilizzare un framework come dozer o altro Java Bean mapping framework.

Se il modello simile a questa:

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

    // constructor and getter method 
} 

La classe involucro potrebbe assomigliare a questo:

public class UserView { 
    private final User user; 

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

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

O anche si può riscrivere tutto l'oggetto:

public class UserView { 
    private final String fullName; 

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

    // getter if needed 
} 
Problemi correlati