使用GSON和TypeAdapter将BSON(mongoDB)读入POJO [英] Read BSON (mongoDB) into POJO using GSON and TypeAdapter

查看:364
本文介绍了使用GSON和TypeAdapter将BSON(mongoDB)读入POJO的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找一种使用GSON将MongoDB文档读入POJO的方法。它可以很好地工作,直到遇到诸如date和longs之类的东西。



我想为Gson编写一个自定义适配器,它将转换任何长度为BSON的编码。阅读发布我已创建我自己的适配器:

  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();
断言$ numberLong.equals(in.nextName());
Long value = in.nextLong();
in.endObject();
返回值;




$ b $ p
$ b我已经定义了以下测试来检查这个工作是否正常:

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

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

私有静态类MyTestObject
{
long timestamp;

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

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


$ / code>

第一个(写入)测试工作得很好,但阅读测试失败:

  com.google.gson.JsonSyntaxException:java.lang.IllegalStateException:预计会长,但是BEGIN_OBJECT在第1行第15列路径$ .timestamp 

因为从我的适配器读取函数永远不会被调用。我推测这可能是因为我想映射到MyTestObject而不是Long,但我不想为所有包含long的类编写适配器。



是否可以编写一个适用于GSON的适配器来转换所有发送给它的BSON多长时间?解决方案

我使用CustomizedTypeAdapterFactory 。 看到这个问题 b
$ b

基本上先写一个自定义的适配器:

  public abstract class CustomizedTypeAdapterFactory< c取代; 
实现TypeAdapterFactory
{
private final Class< C> customizedClass;

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

@SuppressWarnings(unchecked)//我们使用运行时检查来保证'C'和'T'相等
public final< T> TypeAdapter< T> create(Gson gson,TypeToken< T> type){
return type.getRawType()== customizedClass
? (TypeAdapter< T>)customizeMyClassAdapter(gson,(TypeToken< C>)类型)
:null;
}

私人TypeAdapter< C> customizeMyClassAdapter(Gson gson,TypeToken< C>类型){
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);
返回delegate.fromJsonTree(tree);
}
};
}

/ **
*在写入
*输出的JSON流之前,用{@code toSerialize}覆盖它。
* /
protected void beforeWrite(C source,JsonElement toSerialize){
}
$ b $ / **
*用代码覆盖反序列化},然后将其解析为
*应用程序类型。
* /
protected void afterRead(JsonElement反序列化){
}
}

然后为所有需要考虑的类创建一个子类。您必须为每个包含long(在本例中)的类创建一个。但是你不需要序列化任何东西,除了长整型值(以及任何其他特定于bson的值)以外。

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

@Override
protected void beforeWrite(MyTestObject source,JsonElement toSerialize)
{
//你可以在这里转换其他方式,I让mongo的文档解析器来处理这个问题。
}

@Override
protected void afterRead(JsonElement反序列化)
{
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(); 


I'm looking for a way to read a MongoDB document into a POJO using GSON. It works just fine until you run into stuff like date's and longs.

I would like to write a custom adapter for Gson which will convert any BSON encoded long. Reading this post I have created my own adapter:

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

I have defined the following tests to check if this works:

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

The first (write) test works just fine, but the read test fails on:

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

Because the read function from my adapter is never called. I presume this might be because I want to map to MyTestObject and not to Long, but I don't want to have to write adapters for all classes that contain longs.

Is it possible to write an adapter for GSON that converts all BSON longs I send into it?

解决方案

I solved it using a CustomizedTypeAdapterFactory. See this question

Basically first write a customized adapter:

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

And then create a subclass for all classes that need to be taken into account. You do have to create one for every class containing a long (in this case). But you don't have to serialize anything but the long value (and any other bson specific values)

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

and then generate Gson with:

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

这篇关于使用GSON和TypeAdapter将BSON(mongoDB)读入POJO的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆