使用GSON和Hibernate存储任意数据

时间:2017-02-27 19:44:08

标签: java json hibernate gson

我想仅保留一些与客户相关的数据。我打算在这里故意忽略数据库规范化,因为数据在服务器端是没用的。

我可以通过让客户端将其转换为JSON并在请求中的JSON发送中包含String来轻松地完成。这感觉非常错误。我试着做一些更聪明的事情并且失败了。

我想拥有什么:

鉴于

class MyEntity {
    String someString;
    int someInt;
    @Lob String clientData;
}

和输入

{
    someString: "The answer",
    someInt: 43,
    clientData: {
        x: [1, 1, 2, 3, 5, 8, 13],
        y: [1, 1, 2, 6, 24, 120],
        tonsOfComplicatedStuff: {stuff: stuff}
    }
}

clientData打包为JSON打包在一个列中。请注意我不想为MyEntity 编写适配器,因为有很多列。我需要一个适用于单列的适配器。列类型不必是字符串(Serializable或其他任何东西,因为服务器实际上并不关心)。

1 个答案:

答案 0 :(得分:1)

Gson支持@JsonAdapter注释,允许指定JSON(de)序列化程序,类型适配器,甚至类型适配器工厂。注释看起来很适合注释clientData中的MyEntity字段:

final class MyEntity {

    String someString;

    int someInt;

    @Lob
    @JsonAdapter(PackedJsonTypeAdapterFactory.class)
    String clientData;

}

类型适配器工厂可能如下所示:

final class PackedJsonTypeAdapterFactory
        implements TypeAdapterFactory {

    // Gson can instantiate this itself
    private PackedJsonTypeAdapterFactory() {
    }

    @Override
    public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
        @SuppressWarnings("unchecked")
        final TypeAdapter<T> typeAdapter = (TypeAdapter<T>) new PackedJsonTypeAdapter(gson);
        return typeAdapter;
    }

    private static final class PackedJsonTypeAdapter
            extends TypeAdapter<String> {

        private final Gson gson;

        private PackedJsonTypeAdapter(final Gson gson) {
            this.gson = gson;
        }

        @Override
        public void write(final JsonWriter out, final String json) {
            final JsonElement jsonElement = gson.fromJson(json, JsonElement.class);
            gson.toJson(jsonElement, out);
        }

        @Override
        public String read(final JsonReader in) {
            final JsonElement jsonElement = gson.fromJson(in, JsonElement.class);
            return jsonElement != null ? jsonElement.toString() : null;
        }

    }

}

请注意,此转换器策略是作为类型适配器工厂实现的,因为这是访问我已知的Gson实例的唯一方法,并且JsonSerializer / JsonDeserializer似乎不合适通过序列化上下文进行良好的解析。这里的另一个缺陷是这个实现是基于树的,要求JSON树完全存储在内存中。理论上,可能有一个很好的面向流的实现,如gson.fromJson(jsonReader) -> JsonReaderJsonReader - &gt; Reader装饰器,例如,重定向到StringWriter,但我不能很长一段时间没有找到任何替代方案。

public static void main(final String... args) {
    final Gson gson = new Gson();

    out.println("deserialization:");
    final String incomingJson = "{someString:\"The answer\",someInt:43,clientData:{x:[1,1,2,3,5,8,13],y:[1,1,2,6,24,120],tonsOfComplicatedStuff:{stuff:stuff}}}";
    final MyEntity myEntity = gson.fromJson(incomingJson, MyEntity.class);
    out.println("\t" + myEntity.someString);
    out.println("\t" + myEntity.someInt);
    out.println("\t" + myEntity.clientData);

    out.println("serialization:");
    final String outgoingJson = gson.toJson(myEntity);
    out.println("\t" + outgoingJson);

    out.println("equality check:");
    out.println("\t" + areEqual(gson, incomingJson, outgoingJson));
}

private static boolean areEqual(final Gson gson, final String incomingJson, final String outgoingJson) {
    final JsonElement incoming = gson.fromJson(incomingJson, JsonElement.class);
    final JsonElement outgoing = gson.fromJson(outgoingJson, JsonElement.class);
    return incoming.equals(outgoing);
}

输出:

deserialization:  
    The answer  
    43  
    {"x":[1,1,2,3,5,8,13],"y":[1,1,2,6,24,120],"tonsOfComplicatedStuff":{"stuff":"stuff"}}  
serialization:  
    {"someString":"The answer","someInt":43,"clientData":{"x":[1,1,2,3,5,8,13],"y":[1,1,2,6,24,120],"tonsOfComplicatedStuff":{"stuff":"stuff"}}}  
equality check:  
    true  

不知道它是否可以很好地使用Hibernate。

修改

尽管JSON打包的字符串被收集到内存中,但由于各种原因,流式传输可能更便宜并且可以节省一些内存。流式传输的另一个优点是,这样的JSON打包类型适配器不再需要类型适配器工厂,因此Gson实例保持JSON流的原样,但仍然进行一些标准化,如{stuff:stuff} - &gt ; {"stuff":"stuff"}。例如:

@JsonAdapter(PackedJsonStreamTypeAdapter.class)
String clientData;
final class PackedJsonStreamTypeAdapter
        extends TypeAdapter<String> {

    private PackedJsonStreamTypeAdapter() {
    }

    @Override
    public void write(final JsonWriter out, final String json)
            throws IOException {
        @SuppressWarnings("resource")
        final Reader reader = new StringReader(json);
        writeNormalizedJsonStream(new JsonReader(reader), out);
    }

    @Override
    public String read(final JsonReader in)
            throws IOException {
        @SuppressWarnings("resource")
        final Writer writer = new StringWriter();
        writeNormalizedJsonStream(in, new JsonWriter(writer));
        return writer.toString();
    }

}
final class JsonStreams {

    private JsonStreams() {
    }

    static void writeNormalizedJsonStream(final JsonReader reader, final JsonWriter writer)
            throws IOException {
        writeNormalizedJsonStream(reader, writer, true);
    }

    @SuppressWarnings("resource")
    static void writeNormalizedJsonStream(final JsonReader reader, final JsonWriter writer, final boolean isLenient)
            throws IOException {
        int level = 0;
        for ( JsonToken token = reader.peek(); token != null; token = reader.peek() ) {
            switch ( token ) {
            case BEGIN_ARRAY:
                reader.beginArray();
                writer.beginArray();
                ++level;
                break;
            case END_ARRAY:
                reader.endArray();
                writer.endArray();
                if ( --level == 0 && isLenient ) {
                    return;
                }
                break;
            case BEGIN_OBJECT:
                reader.beginObject();
                writer.beginObject();
                ++level;
                break;
            case END_OBJECT:
                reader.endObject();
                writer.endObject();
                if ( --level == 0 && isLenient ) {
                    return;
                }
                break;
            case NAME:
                final String name = reader.nextName();
                writer.name(name);
                break;
            case STRING:
                final String s = reader.nextString();
                writer.value(s);
                break;
            case NUMBER:
                final String rawN = reader.nextString();
                final Number n;
                final Long l = Longs.tryParse(rawN);
                if ( l != null ) {
                    n = l;
                } else {
                    final Double d = Doubles.tryParse(rawN);
                    if ( d != null ) {
                        n = d;
                    } else {
                        throw new AssertionError(rawN); // must never happen
                    }
                }
                writer.value(n);
                break;
            case BOOLEAN:
                final boolean b = reader.nextBoolean();
                writer.value(b);
                break;
            case NULL:
                reader.nextNull();
                writer.nullValue();
                break;
            case END_DOCUMENT:
                // do nothing
                break;
            default:
                throw new AssertionError(token);
            }
        }
    }

}

这个分别解析并生成相同的输入和输出。 Longs.tryParseDoubles.tryParse方法取自Google Guava。