6

次のような JSON ファイルを読み取ろうとしています。

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

... の部分は、次のようなタイプに基づいて交換可能です。

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

また:

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

私の人生では、これを実現するために使用する適切な JAXB 注釈/クラスを理解できません。必要に応じて、型変数をデータ ブロックの外に移動しても問題はありません。

Glassfish 3.1.2.2 を使用しています。

編集:

Perception によって提供されたコードに基づいて、私は簡単な試みをしました... ただし、グラスフィッシュでは機能しません:

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

そして、それを取り込む簡単な POST:

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

次のようにJSONを渡すと:

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

次のようなメッセージが表示されます。

Can not construct instance of Data, problem: abstract types can only be instantiated with additional type information
 at [Source: org.apache.catalina.connector.CoyoteInputStream@28b92ec1; line: 2, column: 5] (through reference chain: DataWrapper["data"])
4

1 に答える 1

11

すべてのサブクラスを指定する注釈をDataClass追加します。@XmlSeeAlso

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

次に、各サブクラスで@XmlType注釈を使用して型名を指定します。

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

アップデート

注: 私はEclipseLink JAXB (MOXy)のリーダーであり、JAXB (JSR-222)エキスパート グループのメンバーです。

JAXB (JSR-222) 仕様は、JSON バインディングをカバーしていません。JAX-RS で JAXB アノテーションを介して JSON マッピングを指定する方法はいくつかあります。

  1. JAXB 実装と、StAX イベントを JSON に変換する Jettison などのライブラリ (参照: http://blog.bdoughan.com/2011/04/jaxb-and-json-via-jettison.html )
  2. JSON バインディングを提供する JAXB impl を活用する (参照: http://blog.bdoughan.com/2011/08/json-binding-with-eclipselink-moxy.html )
  3. 一部の JAXB メタデータ (Jackson など) のサポートを提供する JSON バインディング ツールを活用します。

モデルがアノテーションに期待どおりに反応していないように見えるので、シナリオ 3 を使用していると思います。以下では、シナリオ 2 を使用しているかのようにソリューションを示します。

データラッパー

import javax.xml.bind.annotation.*;

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

    private String a;
    private Data data;

}

データ

import javax.xml.bind.annotation.*;

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

}

データサブA

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

MOXy を JAXB プロバイダーとして指定するにはjaxb.properties、次のエントリを使用して、ドメイン モデルと同じパッケージで呼び出されるファイルを含める必要があります ( http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-asを参照)。 -your.html ):

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

デモ

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/出力

MOXy は継承インジケータとして数値を読み込むことができます2が、現在は常に として書き出します"2"。この問題に対処するために、次の拡張リクエストをオープンしました: http://bugs.eclipse.org/407528

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

詳細については

次のリンクは、JAX-RS 実装で MOXy を使用するのに役立ちます。

于 2013-05-08T00:24:54.480 に答える