2013-05-07 17 views
6

Sto cercando di leggere un file JSON come:JAXB ed eredità

{ 
    "a": "abc", 
    "data" : { 
     "type" : 1, 
     ... 
    } 
} 

dove la ... parte è sostituibile in base al tipo come:

{ 
    "a": "abc", 
    "data" : { 
     "type" : 1, 
     "b" : "bcd" 
    } 
} 

o:

{ 
    "a": "abc", 
    "data" : { 
     "type" : 2, 
     "c" : "cde", 
     "d" : "def", 
    } 
} 

Per la vita di me non riesco a capire le giuste annotazioni/classi JAXB da usare per far sì che ciò accada. Non ho problemi a spostare la variabile di tipo all'esterno del blocco dati, se necessario.

Sto usando Glassfish 3.1.2.2.

Edit:

Basato sul codice fornito da Perception ho fatto un rapido tentativo ... non funziona in GlassFish però:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, property = "type") 
@JsonSubTypes(
{ 
    @JsonSubTypes.Type(value = DataSubA.class, name = "1"), 
    @JsonSubTypes.Type(value = DataSubB.class, name = "2") 
}) 
@XmlRootElement 
public abstract class Data implements Serializable 
{ 
    private static final long serialVersionUID = 1L; 

    public Data() 
    { 
     super(); 
    } 
} 

@XmlRootElement 
@XmlAccessorType(XmlAccessType.NONE) 
public class DataSubA 
    extends Data 
{ 
    private static final long serialVersionUID = 1L; 

    @XmlElement 
    private BigDecimal expenditure; 

    public DataSubA() { 
     super(); 
    } 

    public DataSubA(final BigDecimal expenditure) { 
     super(); 
     this.expenditure = expenditure; 
    } 

    @Override 
    public String toString() { 
     return String.format("%s[expenditure = %s]\n", 
          getClass().getSimpleName(), getExpenditure()); 
    } 

    public BigDecimal getExpenditure() { 
     return expenditure; 
    } 

    public void setExpenditure(BigDecimal expenditure) { 
     this.expenditure = expenditure; 
    } 
} 

@XmlRootElement 
@XmlAccessorType(XmlAccessType.NONE) 
public class DataSubB 
    extends Data 
{ 
    private static final long serialVersionUID = 1L; 

    @XmlElement 
    private String name; 

    @XmlElement 
    private Integer age; 

    public DataSubB() 
    { 
     super(); 
    } 

    public DataSubB(final String name, final Integer age) 
    { 
     super(); 
     this.name = name; 
     this.age = age; 
    } 

    @Override 
    public String toString() 
    { 
     return String.format("%s[name = %s, age = %s]\n", 
          getClass().getSimpleName(), getName(), getAge()); 
    } 

    public String getName() { 
     return name; 
    } 

    public void setName(String name) { 
     this.name = name; 
    } 

    public Integer getAge() { 
     return age; 
    } 

    public void setAge(Integer age) { 
     this.age = age; 
    } 
} 

@XmlRootElement 
@XmlAccessorType(XmlAccessType.NONE) 
public class DataWrapper 
{ 
    @XmlElement 
    private Data data; 

    public Data getData() { 
     return data; 
    } 

    public void setData(Data data) { 
     this.data = data; 
    } 
} 

E un semplice post che prende in:

@Stateless 
@Path("x") 
public class Endpoint 
{ 
    @POST 
    @Consumes(
    { 
     MediaType.APPLICATION_JSON, 
    }) 
    @Produces(
    { 
     MediaType.APPLICATION_JSON, 
    }) 
    public String foo(final DataWrapper wrapper) 
    { 
     return ("yay"); 
    } 
} 

Quando passo in JSON come:

{ 
    "data" : 
    { 
     "type" : 1, 
     "expenditure" : 1 
    } 
} 

ottengo un messaggio del tipo:

Can not construct instance of Data, problem: abstract types can only be instantiated with additional type information 
at [Source: [email protected]; line: 2, column: 5] (through reference chain: DataWrapper["data"]) 

risposta

11

Sulla DataClass aggiungere un @XmlSeeAlso annotazioni che specifica tutte le sottoclassi:

@XmlRootElement 
@XmlSeeAlso({DataSubA.class, DataSubB.class}) 
public abstract class Data implements Serializable { 

Poi su ciascuna delle sottoclassi utilizzare il @XmlType annotazioni per specificare il tipo nome.

@XmlType(name="1") 
public class DataSubA extends Data { 

UPDATE

Nota: Sono in vantaggio EclipseLink JAXB (MOXy) e un membro del gruppo di esperti JAXB (JSR-222).

Le specifiche JAXB (JSR-222) non coprono il binding JSON. Ci sono diversi modi JAX-RS consente di specificare la mappatura JSON tramite JAXB annotazioni:

  1. implementazione Un JAXB oltre a una biblioteca come Jettison che converte gli eventi StAX per JSON (vedi: http://blog.bdoughan.com/2011/04/jaxb-and-json-via-jettison.html)
  2. Sfruttando un impl JAXB che offre un collegamento JSON (vedi: http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html)
  3. Utilizzo di uno strumento di collegamento JSON che offre supporto per alcuni metadati JAXB (ad es. Jackson).

Dal momento che il modello non sembra essere reagendo come previsto alle annotazioni sto cercando di indovinare che si sta utilizzando scenario 3. Qui di seguito Mostrerò la soluzione, come se si stesse utilizzando lo scenario 2.

DataWrapper

import javax.xml.bind.annotation.*; 

@XmlRootElement 
@XmlAccessorType(XmlAccessType.FIELD) 
public class DataWrapper { 

    private String a; 
    private Data data; 

} 

dati

import javax.xml.bind.annotation.*; 

@XmlAccessorType(XmlAccessType.FIELD) 
@XmlSeeAlso({DataSubA.class, DataSubB.class}) 
public class Data { 

} 

DataSubA

import javax.xml.bind.annotation.XmlType; 

@XmlType(name="1") 
public class DataSubA extends Data { 

    private String b; 

} 

DataSubB

import javax.xml.bind.annotation.XmlType; 

@XmlType(name="2") 
public class DataSubB extends Data { 

    private String c; 
    private String d; 

} 

jaxb.properties

Per specificare moxy come provider JAXB è necessario includere un file chiamato jaxb.properties nello stesso pacchetto come il modello di dominio con la seguente voce (vedi: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html):

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory 

Demo

import java.util.*; 
import javax.xml.bind.*; 
import javax.xml.transform.stream.StreamSource; 
import org.eclipse.persistence.jaxb.JAXBContextProperties; 

public class Demo { 

    public static void main(String[] args) throws Exception { 
     Map<String, Object> properties = new HashMap<String, Object>(); 
     properties.put(JAXBContextProperties.MEDIA_TYPE, "application/json"); 
     properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false); 
     JAXBContext jc = JAXBContext.newInstance(new Class[] {DataWrapper.class}, properties); 

     Unmarshaller unmarshaller = jc.createUnmarshaller(); 
     StreamSource json = new StreamSource("src/forum16429717/input.json"); 
     DataWrapper dataWrapper = unmarshaller.unmarshal(json, DataWrapper.class).getValue(); 

     Marshaller marshaller = jc.createMarshaller(); 
     marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); 
     marshaller.marshal(dataWrapper, System.out); 
    } 

} 

input.json/Output

moxy può leggere il valore numerico 2 come indicatore eredità, ma attualmente sempre scriverlo come "2". Ho aperto la seguente richiesta di miglioramento per risolvere questo problema: http://bugs.eclipse.org/407528.

{ 
    "a" : "abc", 
    "data" : { 
     "type" : "2", 
     "c" : "cde", 
     "d" : "def" 
    } 
} 

Per ulteriori informazioni

Il seguente link vi aiuterà a utilizzare moxy in un'implementazione JAX-RS.

+0

Nessun cambiamento ...: -/ – TofuBeer

+0

@TofuBeer - Prova ad aggiungere un '@ GET' per vedere che cosa il messaggio di risposta è. Quindi puoi confrontare questo con quello che stai inviando come richiesta. –

+0

{ "dati": { "name": "A", "età": 42 }} – TofuBeer