2016-03-21 8 views
1

Ich bin auf der Suche nach einer Möglichkeit, ein MongoDB-Dokument in ein POJO mit GSON zu lesen. Es funktioniert gut, bis Sie auf Dinge wie Datum und Longs stoßen.Lesen BSON (MongoDB) in POJO mit GSON und TypeAdapter

Ich möchte einen benutzerdefinierten Adapter für Gson schreiben, die alle BSON codiert lang konvertieren wird. Lesen this Post Ich habe meine eigenen Adapter erstellt:

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

Ich habe die folgenden Tests definiert zu überprüfen, ob dies funktioniert:

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

Der erste (schreiben) Test ganz gut funktioniert, aber den Lesetest scheitert an:

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

Weil die Lesefunktion von meinem Adapter nie aufgerufen wird. Ich nehme an, dass dies möglicherweise so ist, weil ich MyTestObject und nicht Long zuordnen möchte, aber ich möchte nicht Adapter für alle Klassen schreiben müssen, die Longs enthalten.

Ist es möglich, einen Adapter für GSON zu schreiben, der alle BSON-Longs konvertiert, die ich hineinschicke?

Antwort

0

Ich löste es mithilfe einer CustomizedTypeAdapterFactory. See this question

Grundsätzlich zuerst eine angepasste Adapter schreiben:

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) { 
    } 
} 

Und dann eine Unterklasse für alle Klassen erstellen, die berücksichtigt werden müssen. Sie müssen einen für jede Klasse erstellen, die einen Long enthält (in diesem Fall). Aber Sie müssen nicht alles, aber der lange Wert serialisiert (und alle anderen BSON spezifischen Werte)

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")); 
    } 
} 

und dann erzeugen Gson mit:

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

Sie den Code fehlen, die dies zu Mongo verbindet , was veranlasst Mongo, Ihren Adapter und nicht BSON zu verwenden? – Dudi

+0

Das liegt an dir. Dieser Code kann verwendet werden, um eine Bson-Zeichenkette (wie mongodb-Ausgabe) in ein Pojo und zurück umzuwandeln. –

+0

AFAIK TypeAdapter registriert im Gson-Objekt wird verwendet, wenn man diesen Gson aufruft. Aber wenn ich ein Dokument in Mongo habe, wenn ich es herausziehe, wird es automatisch versuchen, es zu einem Objekt zu machen. Ich benutze find(), die eine MongoCollection zurückgibt, so dass es automatisch mit dem TypeAdapter deserialize zu T w 0. – Dudi