使用GSON和TypeAdapter将BSON(mongoDB)读入POJO

时间:2016-03-21 15:09:34

标签: java mongodb gson bson

我正在寻找一种使用GSON将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长转换成它?

1 个答案:

答案 0 :(得分: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的每个类创建一个(在本例中)。但是除了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();