2013-05-07 16 views
6

Próbuję odczytać plik JSON jak:JAXB i dziedziczenie

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

... część, gdzie można wymieniać w zależności od typu, takich jak:

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

czyli

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

Dla życia mnie nie mogę znaleźć odpowiednich adnotacji/klas JAXB do użycia, aby tak się stało. Nie mam problemu, przenosząc zmienną typu poza blok danych, jeśli to konieczne.

Używam Glassfish 3.1.2.2.

Edit:

podstawie kodu dostarczonego przez Perception Zrobiłem szybkie próba ... nie działa w GlassFish choć:

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

i prosty post, który zabiera go w:

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

Kiedy mijam w JSON jak:

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

dostaję komunikat:

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"]) 

Odpowiedz

11

Na DataClass dodać @XmlSeeAlso adnotacji, która określa wszystkie podklasy:

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

Następnie na każdej podklasy użyć @XmlType adnotacji, aby określić typ Nazwa.

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

UPDATE

Uwaga: Jestem EclipseLink JAXB (MOXy) prowadzenie i członek grupy JAXB (JSR-222) ekspertów.

Specyfikacja JAXB (JSR-222) nie obejmuje wiązania JSON. Istnieją różne sposoby JAX-RS pozwala określić mapowanie JSON poprzez JAXB adnotacji:

  1. realizacja JAXB plus biblioteka jak jettison który przekształca wydarzenia Stax do JSON (patrz: http://blog.bdoughan.com/2011/04/jaxb-and-json-via-jettison.html)
  2. Wykorzystując do Impl JAXB która oferuje wiązanie JSON (patrz: http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html)
  3. Wykorzystanie narzędzia wiążącego JSON, które oferuje obsługę niektórych metadanych JAXB (np. Jackson).

Ponieważ Twój model nie reaguje zgodnie z oczekiwaniami na adnotacje, domyślam się, że używasz scenariusza 3. Poniżej przedstawię rozwiązanie tak, jakbyś używał scenariusza 2.

DataWrapper

import javax.xml.bind.annotation.*; 

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

    private String a; 
    private Data data; 

} 

danych

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

Aby określić Moxy jako dostawcę JAXB musisz dołączyć plik o nazwie jaxb.properties w tym samym opakowaniu jak model domeny z następującego wpisu (patrz: 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 może odczytać wartości liczbowej 2 jak wskaźnik spadków, ale obecnie to zawsze napisać go jako "2". Otworzyłem następującą prośbę o ulepszenie w celu rozwiązania tego problemu: http://bugs.eclipse.org/407528.

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

Aby uzyskać więcej informacji

Poniższy link pomoże Ci wykorzystać Moxy w realizacji JAX-RS.

+0

Bez zmian ...: -/ – TofuBeer

+0

@TofuBeer - Spróbuj dodać '@ GET' aby zobaczyć co jest przesłanie odpowiedzi. Następnie możesz porównać to z tym, co wysyłasz jako żądanie. –

+0

{ "dane": { "name": "A", "wiek": 42 } } – TofuBeer