Tipe AutoBean berparameter yang berisi anggota yang diketik

Pertanyaan

Apakah ada cara untuk melakukan deserialisasi JSON menggunakan kerangka AutoBean sehingga kacang yang dihasilkan memiliki parameter tipe yang memengaruhi tipe satu atau lebih anggotanya?

Latar belakang

RPC dengan hasil JSON

Saya menggunakan GWT (RequestBuilder) untuk melakukan permintaan RPC. Payload JSON yang dikembalikan adalah dalam bentuk berikut:

{
  "resultSet": [{...}, {...}, ...], // items requested; say, items 150-160
  "totalCount": 15330               // total matching items in DB
}

Objek di resultSet jenisnya bervariasi tergantung pada RPC spesifik yang saya panggil.

Antarmuka AutoBean

Saya ingin membatalkan serialisasi JSON ini menggunakan AutoBean. Saya mencoba merepresentasikan objek ini sebagai berikut:

interface RpcResults<T> {

  List<T> getResultSet();
  void setResultSet(List<T> resultSet);

  int getTotalCount();
  void setTotalCount(int totalCount);

}

Saya juga telah membuat antarmuka yang sesuai yang mewakili setiap jenis objek yang mungkin ada dalam resultSet. Terakhir, saya mengatur panggilan yang sesuai ke AutoBeanCodex.decode.

Menjalankan kode

Mencoba menjalankan kode ini dalam mode pengembangan menyebabkan pelacakan tumpukan berikut muncul di konsol:

19:44:23.791 [ERROR] [xcbackend] Uncaught exception escaped
java.lang.IllegalArgumentException: The AutoBeanFactory cannot create a java.lang.Object
    at com.google.gwt.autobean.shared.AutoBeanCodex$Decoder.push(AutoBeanCodex.java:240)
    at com.google.gwt.autobean.shared.AutoBeanCodex$Decoder.decode(AutoBeanCodex.java:50)
    at com.google.gwt.autobean.shared.AutoBeanCodex$Decoder.visitCollectionProperty(AutoBeanCodex.java:83)
    at com.citrix.xenclient.backend.client.json.RpcResultsAutoBean.traverseProperties(RpcResultsAutoBean.java:100)
    at com.google.gwt.autobean.shared.impl.AbstractAutoBean.traverse(AbstractAutoBean.java:153)
    at com.google.gwt.autobean.shared.impl.AbstractAutoBean.accept(AbstractAutoBean.java:112)
    at com.google.gwt.autobean.shared.AutoBeanCodex$Decoder.decode(AutoBeanCodex.java:51)
    at com.google.gwt.autobean.shared.AutoBeanCodex.decode(AutoBeanCodex.java:505)
    at com.google.gwt.autobean.shared.AutoBeanCodex.decode(AutoBeanCodex.java:521)
    at com.citrix.xenclient.backend.client.services.JSONResponseResultSetHandler.onResponseReceived(JSONResponseResultSetHandler.java:51)
    at com.google.gwt.http.client.Request.fireOnResponseReceived(Request.java:287)
    at com.google.gwt.http.client.RequestBuilder$1.onReadyStateChange(RequestBuilder.java:395)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at com.google.gwt.dev.shell.MethodAdaptor.invoke(MethodAdaptor.java:103)
    at com.google.gwt.dev.shell.MethodDispatch.invoke(MethodDispatch.java:71)
    at com.google.gwt.dev.shell.OophmSessionHandler.invoke(OophmSessionHandler.java:157)
    at com.google.gwt.dev.shell.BrowserChannelServer.reactToMessagesWhileWaitingForReturn(BrowserChannelServer.java:326)
    at com.google.gwt.dev.shell.BrowserChannelServer.invokeJavascript(BrowserChannelServer.java:207)
    at com.google.gwt.dev.shell.ModuleSpaceOOPHM.doInvoke(ModuleSpaceOOPHM.java:126)
    at com.google.gwt.dev.shell.ModuleSpace.invokeNative(ModuleSpace.java:561)
    at com.google.gwt.dev.shell.ModuleSpace.invokeNativeObject(ModuleSpace.java:269)
    at com.google.gwt.dev.shell.JavaScriptHost.invokeNativeObject(JavaScriptHost.java:91)
    at com.google.gwt.core.client.impl.Impl.apply(Impl.java)
    at com.google.gwt.core.client.impl.Impl.entry0(Impl.java:214)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at com.google.gwt.dev.shell.MethodAdaptor.invoke(MethodAdaptor.java:103)
    at com.google.gwt.dev.shell.MethodDispatch.invoke(MethodDispatch.java:71)
    at com.google.gwt.dev.shell.OophmSessionHandler.invoke(OophmSessionHandler.java:157)
    at com.google.gwt.dev.shell.BrowserChannelServer.reactToMessages(BrowserChannelServer.java:281)
    at com.google.gwt.dev.shell.BrowserChannelServer.processConnection(BrowserChannelServer.java:531)
    at com.google.gwt.dev.shell.BrowserChannelServer.run(BrowserChannelServer.java:352)
    at java.lang.Thread.run(Thread.java:636)

Berdasarkan jejak tumpukan ini, firasat saya adalah sebagai berikut:

  1. Jenis penghapusan membuatnya tampak bahwa RpcResults.getResultSet() mengembalikan List mentah.
  2. Deserialiser AutoBean mencoba membuat Object instance untuk setiap item di resultSet.
  3. Kegagalan

Pertanyaan lagi

Apakah saya melewatkan sesuatu di AutoBean API yang memungkinkan saya melakukan ini dengan mudah? Jika tidak, apakah ada titik serangan yang harus saya perhatikan? Apakah ada alternatif yang lebih masuk akal untuk apa yang saya lakukan (selain JSONParser dan JavaScriptObject, yang sudah saya gunakan)?


person Wesley    schedule 15.03.2011    source sumber


Jawaban (2)


Ini tidak sederhana, karena penghapusan tipe Java. Tipe T tidak ada saat runtime, telah dihapus menjadi Object sebagai pengganti batas bawah lainnya. AutoBeanCodex memerlukan informasi tipe untuk mereifikasi elemen payload json yang masuk. Informasi jenis ini biasanya disediakan oleh implementasi AutoBean, namun karena penghapusan T, yang diketahui hanyalah bahwa informasi tersebut berisi file List<Object>.

Jika Anda dapat memberikan literal kelas saat runtime, pengambil dapat dideklarasikan sebagai Splittable getResultSet() dan masing-masing elemen daftar direifikasi dengan memanggil AutoBeanCodex.decode(autoBeanFactory, SomeInterfaceType.class, getResultSet().get(index)). Dengan menggunakan Category, Anda dapat menambahkan metode <T> T getResultAs(Class<T> clazz, int index) ke antarmuka AutoBean. Ini akan terlihat seperti:

@Category(MyCategory.class)
interface MyFactory extends AutoBeanFactory {
  AutoBean<ResultContainer> resultContainer();
}
interface ResultContainer<T> {
  Splittable getResultSet();
  // It's the class literal that makes it work
  T getResultAs(Class<T> clazz, int index);
}
class MyCategory {
  public static <T> T getResultAs(Autobean<ResultContainer> bean,
      Class<T> clazz, int index) {
    return AutoBeanCodex.decode(bean.getFactory(), clazz,
      bean.as().getResultSet().get(index)).as();
  }
}
person BobV    schedule 15.03.2011
comment
Pendekatan ini sangat masuk akal bagi saya. Namun, bahkan dengan GWT 2.2.0, tampaknya tipe generik tidak berfungsi sebagai parameter metode kategori. Artinya tidak mungkin meneruskan objek kelas ke getResultAs. Saya telah menyederhanakan contoh Anda menjadi paste.pocoo.org/show/354352, namun kompiler GWT mengeluh dengan paste.pocoo.org/show/354358. Tanpa bisa meneruskan objek kelas, solusi yang cukup elegan ini tidak akan berhasil. - person Wesley; 16.03.2011
comment
Saya pikir Anda memiliki kesalahan ketik dalam kategori Anda. Anda menambahkan metode getResultSet() alih-alih menamainya getResultAs(). - person BobV; 16.03.2011
comment
Kamu benar. Memperbaiki kesalahan ketik itu memperbaiki kasus sederhana yang saya tempel. Mencoba mendekati kode dalam jawaban Anda, saya berakhir dengan paste.pocoo.org/show/ 354669. Namun, hal ini menyebabkan kesalahan kompilasi paste.pocoo.org/show/354671. (Saya mencoba AutoBean<ResultContainer<T>> bean dan AutoBean<ResultContainer> bean pada tanda tangan ResultCategory.getResultAs, dengan hasil yang sama.) Apakah saya masih membuat kesalahan nyata di sini? - person Wesley; 16.03.2011

Coba ganti metode .getResultSet() dan .setResultSet() di antarmuka spesifik objek Anda:

interface FooRpcResults extends RpcResults<Foo> {
    @Override
    List<Foo> getResultSet();
    @Override
    void setResultSet(List<Foo> value);
}

Tes berikut berfungsi untuk saya (GWT 2.3.0):

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanCodex;
import com.google.web.bindery.autobean.shared.AutoBeanFactory;
import com.google.web.bindery.autobean.shared.AutoBeanUtils;
import com.google.web.bindery.autobean.vm.AutoBeanFactorySource;

public class AutoBeanTest {
 public static interface Page<T> {
  int getDataSize();

  List<T> getPage();

  int getStartIndex();

  void setDataSize(int value);

  void setPage(List<T> value);

  void setStartIndex(int value);
 }

 public static interface Thing {
  String getName();

  void setName(String value);
 }

 public static interface ThingFactory extends AutoBeanFactory {
  AutoBean<Thing> createThing();

  AutoBean<ThingPage> createThingPage();
 }

 public static interface ThingPage extends Page<Thing> {
  @Override
  List<Thing> getPage();

  @Override
  void setPage(List<Thing> value);
 }

 @Test
 public void testAutoBean() {
  final ThingFactory factory = AutoBeanFactorySource
    .create(ThingFactory.class);

  final Thing thing1 = factory.createThing().as();
  thing1.setName("One");

  final Thing thing2 = factory.createThing().as();
  thing2.setName("Two");

  final List<Thing> things = new ArrayList<Thing>();
  things.add(thing1);
  things.add(thing2);

  final Page<Thing> page = factory.createThingPage().as();
  page.setStartIndex(50);
  page.setDataSize(1000);
  page.setPage(things);

  final String json = AutoBeanCodex.encode(
    AutoBeanUtils.getAutoBean(page)).getPayload();

  final Page<Thing> receivedPage = AutoBeanCodex.decode(factory,
    ThingPage.class, json).as();

  assertEquals(receivedPage.getStartIndex(), page.getStartIndex());
  assertEquals(receivedPage.getDataSize(), page.getDataSize());
  assertNotNull(receivedPage.getPage());
  assertEquals(receivedPage.getPage().size(), page.getPage().size());
  for (int i = 0; i < receivedPage.getPage().size(); i++) {
   assertNotNull(receivedPage.getPage().get(i));
   assertEquals(receivedPage.getPage().get(i).getName(), page
     .getPage().get(i).getName());
  }
 }
}

Menghapus override di antarmuka ThingPage akan merusaknya.

person Ivan B    schedule 04.05.2011