【问题标题】:Read BSON (mongoDB) into POJO using GSON and TypeAdapter使用 GSON 和 TypeAdapter 将 BSON (mongoDB) 读入 POJO
【发布时间】:2016-03-21 15:09:34
【问题描述】:

我正在寻找一种使用 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 long 转换为它?

【问题讨论】:

    标签: java mongodb gson bson


    【解决方案1】:

    我使用 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();
    

    【讨论】:

    • 您缺少将其连接到 Mongo 的代码,是什么原因导致 Mongo 使用您的适配器而不是 BSON?
    • 这取决于你。此代码可用于将 bson 字符串(如 mongodb 输出)转换为 pojo 并返回。
    • 在 gson 对象中注册的 AFAIK TypeAdapter 将在调用该 gson 时使用。但是如果我在 mongo 中有一个文档,当我把它拉出来时,它会自动尝试使它成为一个对象。我正在使用返回 MongoCollection 的 find(),因此它使用 TypeAdapter 自动反序列化为 T w\o
    • 这个 q/a 是关于 GSON 而不是 MongoCollection 或您使用的自动反序列化。但是,如果您对此提出问题并指出我会尽力提供帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-04-02
    • 1970-01-01
    • 2018-12-25
    • 2013-02-02
    • 2023-03-12
    • 1970-01-01
    • 2015-09-27
    相关资源
    最近更新 更多