2016-03-21 31 views
1

我正在尋找一種使用GSON將MongoDB文檔讀入POJO的方法。它運行得很好,直到遇到像日期和長期的東西。使用GSON和TypeAdapter將BSON(mongoDB)讀入POJO

我想爲Gson編寫一個自定義的適配器,它可以轉換任何長的BSON編碼。閱讀this文章中,我創建了我自己的適配器:

public class BsonLongTypeAdapter extends TypeAdapter<Long> 
{ 
    @Override 
    public void write(JsonWriter out, Long value) throws IOException 
    { 
     out.beginObject() 
      .name("$numberLong") 
      .value(value.toString()) 
      .endObject(); 
    } 

    @Override 
    public Long read(JsonReader in) throws IOException 
    { 
     in.beginObject(); 
     assert "$numberLong".equals(in.nextName()); 
     Long value = in.nextLong(); 
     in.endObject(); 
     return value; 
    } 
} 

我已經定義了下列測試檢查,如果這個工程:

@Test 
public void canWriteCorrectJSON() { 
    Gson gson = new GsonBuilder().registerTypeAdapter(Long.class, new BsonLongTypeAdapter()).create(); 
    MyTestObject obj = new MyTestObject(1458569479431L); 
    String gsonString = gson.toJson(obj); 
    assertEquals("{\"timestamp\":{\"$numberLong\":\"1458569479431\"}}",gsonString); 
} 

@Test 
public void canReadFromJSON() { 
    Gson gson = new GsonBuilder().registerTypeAdapter(Long.class, new BsonLongTypeAdapter()).create(); 
    MyTestObject actualTaskObject = gson.fromJson("{\"timestamp\":{\"$numberLong\":\"1458569479431\"}}", MyTestObject.class); 
    MyTestObject taskObject = new MyTestObject(1458569479431L); 
    assertEquals(taskObject.getTimestamp(),actualTaskObject.getTimestamp()); 
} 

private static class MyTestObject 
{ 
    long timestamp; 

    public MyTestObject(long ts) 
    { 
     timestamp = ts; 
    } 
    public long getTimestamp() 
    { 
     return timestamp; 
    } 

    public void setTimestamp(long timestamp) 
    { 
     this.timestamp = timestamp; 
    } 
} 

第(寫)測試工作得很好,但在讀取測試失敗:

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a long but was BEGIN_OBJECT at line 1 column 15 path $.timestamp 

因爲從我的適配器讀取功能從未被調用。我認爲這可能是因爲我想映射到MyTestObject而不是Long,但我不想爲所有包含long的類編寫適配器。

是否可以爲GSON編寫一個適配器來轉換我發送給它的所有BSON多長時間?

回答

0

我使用CustomizedTypeAdapterFactory解決了它。 See this question

基本上先寫一個自定義適配器:

public abstract class CustomizedTypeAdapterFactory<C> 
     implements TypeAdapterFactory 
{ 
    private final Class<C> customizedClass; 

    public CustomizedTypeAdapterFactory(Class<C> customizedClass) { 
     this.customizedClass = customizedClass; 
    } 

    @SuppressWarnings("unchecked") // we use a runtime check to guarantee that 'C' and 'T' are equal 
    public final <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { 
     return type.getRawType() == customizedClass 
       ? (TypeAdapter<T>) customizeMyClassAdapter(gson, (TypeToken<C>) type) 
       : null; 
    } 

    private TypeAdapter<C> customizeMyClassAdapter(Gson gson, TypeToken<C> type) { 
     final TypeAdapter<C> delegate = gson.getDelegateAdapter(this, type); 
     final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class); 
     return new TypeAdapter<C>() { 
      @Override public void write(JsonWriter out, C value) throws IOException 
      { 
       JsonElement tree = delegate.toJsonTree(value); 
       beforeWrite(value, tree); 
       elementAdapter.write(out, tree); 
      } 
      @Override public C read(JsonReader in) throws IOException { 
       JsonElement tree = elementAdapter.read(in); 
       afterRead(tree); 
       return delegate.fromJsonTree(tree); 
      } 
     }; 
    } 

    /** 
    * Override this to muck with {@code toSerialize} before it is written to 
    * the outgoing JSON stream. 
    */ 
    protected void beforeWrite(C source, JsonElement toSerialize) { 
    } 

    /** 
    * Override this to muck with {@code deserialized} before it parsed into 
    * the application type. 
    */ 
    protected void afterRead(JsonElement deserialized) { 
    } 
} 

,然後爲需要考慮到所有類的子類。您必須爲每個包含long(在本例中)的類創建一個。但是你沒有序列化什麼,但長期價值(以及任何其他BSON具體數值)

public class MyTestObjectTypeAdapterFactory extends CustomizedTypeAdapterFactory<MyTestObject> 
{ 
    public MyTestObjectTypeAdapterFactory() 
    { 
     super(MyTestObject.class); 
    } 

    @Override 
    protected void beforeWrite(MyTestObject source, JsonElement toSerialize) 
    { 
     //you could convert back the other way here, I let mongo's document parser take care of that. 
    } 

    @Override 
    protected void afterRead(JsonElement deserialized) 
    { 
     JsonObject timestamp = deserialized.getAsJsonObject().get("timestamp").getAsJsonObject(); 
     deserialized.getAsJsonObject().remove("timestamp"); 
     deserialized.getAsJsonObject().add("timestamp",timestamp.get("$numberLong")); 
    } 
} 

,然後生成GSON有:

Gson gson = new GsonBuilder().registerTypeAdapterFactory(new MyTestObjectTypeAdapterFactory()).create(); 
+0

你缺少一個以連接到蒙戈代碼,是什麼原因導致Mongo使用你的適配器而不是BSON? – Dudi

+0

這取決於你。此代碼可用於將bson字符串(如mongodb輸出)轉換爲pojo並返回。 –

+0

在調用gson時,將使用在gson對象中註冊的AFAIK TypeAdapter。但是,如果我在mongo中有一個文檔,那麼它會自動嘗試使其成爲一個對象。我使用find()返回一個MongoCollection ,所以它使用TypeAdapter自動反序列化爲T w \ o – Dudi