2013-09-06 9 views
9

Ho bisogno di configurare Jackson in un modo specifico che descriverò di seguito.Come (De) serializzare il campo dall'oggetto in base all'annotazione usando Jackson?

Requisiti

  1. campi segnalate siano serializzati con solo il loro ID:
    • Se il campo è un oggetto normale, serializzare il suo id
    • Se il campo è un insieme di oggetti, serializzare un matrice di
  2. I campi annotati ottengono i loro nomi di proprietà serializzati diversamente:
    • Se il campo è un oggetto normale, aggiungi "_id" suffisso al nome della proprietà
    • Se il campo è una collezione di oggetti, aggiungere "_ids" suffisso al nome della proprietà
  3. Per l'annotazione ero pensando qualcosa di simile a un costume @JsonId, idealmente con un valore opzionale per sovrascrivere il nome proprio come fa @JsonProperty
  4. la proprietà ID dovrebbe essere definito dall'utente, sia usando:
    • Il già esistente di Jackson @JsonIdentityInfo
    • o creando un'altra classe o di un campo di annotazione
    • o decidendo che l'annotazione ispezionare per id proprietà reperibilità (utile per gli scenari di JPA, per esempio)
  5. oggetti deve essere serializzato con un valore radice incapsulato
  6. La denominazione del caso Camel deve essere convertita in minuscolo con trattini bassi
  7. Tutto ciò deve essere deseri alizable (costruendo un'istanza con solo l'id impostato)

Un esempio

Considerando di questi POJO:

//Inform Jackson which property is the id 
@JsonIdentityInfo(
    generator = ObjectIdGenerators.PropertyGenerator.class, 
    property = "id" 
) 
public abstract class BaseResource{ 
    protected Long id; 

    //getters and setters 
} 

public class Resource extends BaseResource{ 
    private String name; 
    @JsonId 
    private SubResource subResource; 
    @JsonId 
    private List<SubResource> subResources; 

    //getters and setters 
} 

public class SubResource extends BaseResource{ 
    private String value; 

    //getters and setters 
} 

Una possibile serializzazione di un'istanza Resource potrebbe essere:

{ 
    "resource":{ 
     "id": 1, 
     "name": "bla", 
     "sub_resource_id": 2, 
     "sub_resource_ids": [ 
      1, 
      2, 
      3 
     ] 
    } 
} 

Finora ...

  • Requisito # 5 può essere realizzato configurando ObjectMapper nel seguente modo:

    objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true); 
    objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true); 
    

    E quindi utilizzando @JsonRootName("example_root_name_here") nel mio POJO di.

  • Requisito # 6 può essere realizzato configurando ObjectMapper nel seguente modo:

    objectMapper.setPropertyNamingStrategy(
        PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); 
    

Come potete vedere ci sono ancora un sacco di requisiti da soddisfare. Per quelli che si chiedono perché ho bisogno di una tale configurazione, è perché sto sviluppando un webservice REST per ember.js (più specificamente Dati di bordo). Apprezzeremmo molto se potessi aiutare con uno qualsiasi dei requisiti.

Grazie!

+0

come è stata l'esperienza? Sono esattamente di fronte a questi requisiti ora. Se potessi fornire le tue decisioni di configurazione, sarebbe molto apprezzato. Grazie – beku8

+0

hai cercato di creare il tuo 'AnnotationIntrospector'? –

+0

Devo anche interagire con i dati di brace. Ho visto nuove versioni di Jackson fornire funzionalità per facilitare questo. Qual è stata la tua soluzione? post scriptum Sto esaminando la risposta approvata per l'intuizione. – ieugen

risposta

7

La maggior parte (tutti?) Delle vostre esigenze può essere realizzata tramite l'uso di un serializzatore contestuale. Prendendo una risposta da ContextualDeserializer for mapping JSON to different types of maps with Jackson e il wiki di Jackson (http://wiki.fasterxml.com/JacksonFeatureContextualHandlers) sono stato in grado di venire con il seguente.

È necessario iniziare con l'annotazione @JsonId, che è la chiave che indica che una proprietà deve utilizzare solo la proprietà Id.

import com.fasterxml.jackson.annotation.*; 
import java.lang.annotation.*; 

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE}) 
@Retention(RetentionPolicy.RUNTIME) 
@JacksonAnnotation // important so that it will get included! 
public @interface JsonId { 
} 

Il prossimo è l'attuale ContextualSerializer, che fa il sollevamento pesante.

import com.fasterxml.jackson.databind.ser.*; 
import com.fasterxml.jackson.databind.*; 
import com.fasterxml.jackson.core.*; 
import java.io.*; 

public class ContextualJsonIdSerializer 
    extends JsonSerializer<BaseResource> 
    implements ContextualSerializer/*<BaseResource>*/ 
{ 
    private ObjectMapper mapper; 
    private boolean useJsonId; 

    public ContextualJsonIdSerializer(ObjectMapper mapper) { this(mapper, false); } 
    public ContextualJsonIdSerializer(ObjectMapper mapper, boolean useJsonId) { 
     this.mapper = mapper; 
     this.useJsonId = useJsonId; 
    } 

    @Override 
    public void serialize(BaseResource br, JsonGenerator jgen, SerializerProvider provider) throws IOException 
    { 
     if (useJsonId) { 
      jgen.writeString(br.getId().toString()); 
     } else { 
      mapper.writeValue(jgen, br); 
     } 
    } 

    @Override 
    public JsonSerializer<BaseResource> createContextual(SerializerProvider config, BeanProperty property) 
      throws JsonMappingException 
    { 
     // First find annotation used for getter or field: 
     System.out.println("Finding annotations for "+property); 

     if (null == property) { 
      return new ContextualJsonIdSerializer(mapper, false); 
     } 

     JsonId ann = property.getAnnotation(JsonId.class); 
     if (ann == null) { // but if missing, default one from class 
      ann = property.getContextAnnotation(JsonId.class); 
     } 
     if (ann == null) {//|| ann.length() == 0) { 
      return this;//new ContextualJsonIdSerializer(false); 
     } 
     return new ContextualJsonIdSerializer(mapper, true); 
    } 
} 

Questa classe guarda BaseResource proprietà e li ispeziona per vedere se l'annotazione @JsonId è presente. Se è solo la proprietà Id viene utilizzata, altrimenti viene utilizzato un valore ObjectMapper per serializzare il valore. Questo è importante perché se si tenta di utilizzare il mapper che è (fondamentalmente) nel contesto dello ContextualSerializer, si otterrà un overflow dello stack poiché alla fine chiamerà questi metodi più e più volte.

La risorsa deve essere simile alla seguente. Ho usato l'annotazione @JsonProperty invece di racchiudere la funzionalità nel ContextualSerializer perché sembrava sciocco reinventare la ruota.

import java.util.*; 
import com.fasterxml.jackson.annotation.*; 

public class Resource extends BaseResource{ 
    private String name; 

    @JsonProperty("sub_resource_id") 
    @JsonId 
    private SubResource subResource; 

    @JsonProperty("sub_resource_ids") 
    @JsonId 
    private List<SubResource> subResources; 

    //getters and setters 
    public String getName() {return name;} 
    public void setName(String name) {this.name = name;} 

    public SubResource getSubResource() {return subResource;} 
    public void setSubResource(SubResource subResource) {this.subResource = subResource;} 

    public List<SubResource> getSubResources() {return subResources;} 
    public void setSubResources(List<SubResource> subResources) {this.subResources = subResources;} 
} 

Infine il metodo che esegue la serializzazione solo crea un ulteriore ObjectMapper e registra un modulo in originale ObjectMapper.

// Create the original ObjectMapper 
ObjectMapper objectMapper = new ObjectMapper(); 
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true); 
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true); 
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); 

// Create a clone of the original ObjectMapper 
ObjectMapper objectMapper2 = new ObjectMapper(); 
objectMapper2.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true); 
objectMapper2.configure(SerializationFeature.WRAP_ROOT_VALUE, true); 
objectMapper2.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); 

// Create a module that references the Contextual Serializer 
SimpleModule module = new SimpleModule("JsonId", new Version(1, 0, 0, null)); 
// All references to SubResource should be run through this serializer 
module.addSerializer(SubResource.class, new ContextualJsonIdSerializer(objectMapper2)); 
objectMapper.registerModule(module); 

// Now just use the original objectMapper to serialize 
+0

Questo ha risposto alla domanda? Se no, allora cosa non ha risposto? Se è così allora potresti accettarlo? – disrvptor

Problemi correlati