本文介绍了使用GSON和Hibernate存储任意数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 我只想保留一些与客户端相关的数据。我在这里故意忽略数据库规范化,因为数据在服务器端非常没用。 我可以通过使客户端将其转换为JSON并在请求中包含JSON发送中的字符串。这感觉很不对。 我想要的是什么: 鉴于 class MyEntity { String someString; int someInt; @Lob字符串clientData; $ / code $ / pre 和一个输入 { someString:答案, someInt:43, clientData:{x:[1,1,2, 3,5,8,13],$ b $由:[1,1,2,6,24,120], tonsOfComplicatedStuff:{stuff:stuff} } } 将 clientData 打包为JSON柱。请注意,我不想为 MyEntity 编写适配器,因为列数很多。我需要一个适用于单列的适配器。列类型不需要是字符串( Serializable 或其他任何东西都可以,因为服务器真的不在意)。 @JsonAdapter 注释,允许指定一个JSON(de)序列化器,类型适配器,甚至是一个类型适配器工厂。注释看起来很适合注释 MyEntity 中的 clientData 字段: final class MyEntity { String someString; int someInt; @Lob @JsonAdapter(PackedJsonTypeAdapterFactory.class)字符串clientData; 类型适配器工厂可能如下所示: final class PackedJsonTypeAdapterFactory implements TypeAdapterFactory { // Gson can实例化它本身 private PackedJsonTypeAdapterFactory(){} @Override public< T> TypeAdapter< T>创建(final Gson gson,final TypeToken< T> typeToken){ @SuppressWarnings(unchecked) final TypeAdapter< T> typeAdapter =(TypeAdapter< T>)new PackedJsonTypeAdapter(gson); 返回typeAdapter; } private static final class PackedJsonTypeAdapter extends TypeAdapter< String> { private final Gson gson; 私人PackedJsonTypeAdapter(最终Gson gson){ this.gson = gson; $ b @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); 返回jsonElement!= null? jsonElement.toString():null; } } } Gson 实例的唯一方式,而 JsonSerializer / JsonDeserializer 似乎不能通过序列化上下文进行良好的解析。这里的另一个缺陷是这个实现是基于树的,需要将JSON树完全存储在内存中。理论上,可能有一个很好的面向流的实现,如 gson.fromJson(jsonReader) - > JsonReader 或 JsonReader - > Reader 装饰器被重定向到 StringWriter ,但我找不到任何替代品。 public static void main(final String ... args){ final Gson gson = new Gson(); out.println(deserialization:); final String incomingJson ={someString:\答案\,someInt:43,clientData:{x:[1,1,2,3,5,8,13],y:[1 ,1,2,6,24,120],tonsOfComplicatedStuff:{东西:东西}}}; 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(equal check:); out.println(\ t+ areEqual(gson,incomingJson,outgoingJson)); $ b 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); 返回incoming.equals(传出); 输出: 反序列化:答案 43 {x:[1,1,2,3,5,8,13], y:[1,1,2,6,24,120],tonsOfComplicatedStuff:{stuff:stuff}} 序列化: {someString:答案, someInt :43, clientData :{ × :[1,1,2,3,5,8,13], Y :[1,1,2,6,24,120], tonsOfComplicatedStuff: {stuff:stuff}}} 相等检查: true 编辑 尽管JSON包装的字符串被收集到内存中,但由于各种原因,流式传输可能更便宜并且可以节省一些内存。流媒体的另一个优点是,这样一个JSON包装类型的适配器不再需要类型适配器工厂,因此 Gson 实例保留了JSON流,但仍然有一些规范化如 {stuff:stuff} - > {stuff:stuff} 。例如: $ b @JsonAdapter(PackedJsonStreamTypeAdapter.class) String clientData; Final class PackedJsonStreamTypeAdapter 扩展了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); $ b @Override public String read(final JsonReader in) throws IOException { @SuppressWarnings(resource) final Writer writer = new StringWriter(); writeNormalizedJsonStream(in,new JsonWriter(writer)); 返回writer.toString(); } } final类JsonStreams { private JsonStreams(){} static void writeNormalizedJsonStream(final JsonReader reader,final JsonWriter writer )抛出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(令牌){ case BEGIN_ARRAY: reader .beginArray(); writer.beginArray(); ++级别; 休息; case END_ARRAY: reader.endArray(); writer.endArray(); if(--level == 0&& isLenient){ return; } break; case BEGIN_OBJECT: reader.beginObject(); writer.beginObject(); ++级别; 休息; case END_OBJECT: reader.endObject(); writer.endObject(); if(--level == 0&& isLenient){ return; } break; case NAME: final String name = reader.nextName(); writer.name(name); 休息; case STRING: final String s = reader.nextString(); writer.value(s); 休息; case NUMBER: final String rawN = reader.nextString(); final数字n; final Long l = Longs.tryParse(rawN); if(l!= null){ n = 1; } else { final Double d = Doubles.tryParse(rawN); if(d!= null){ n = d; } else {抛出新的AssertionError(rawN); // must must happen } } writer.value(n); 休息; case BOOLEAN: final boolean b = reader.nextBoolean(); writer.value(b); 休息; case NULL: reader.nextNull(); writer.nullValue(); 休息; case END_DOCUMENT: //不做任何操作 break; 默认值:抛出新的AssertionError(token); } } } } 这个解析并分别生成相同的输入和输出。 Longs.tryParse 和 Doubles.tryParse 方法取自Google Guava。 I want to persist some data relevant for the client only. I'm going to intentionally ignore database normalization here, as the data is pretty useless on the server side.I can do it trivially by making the client convert it to JSON and include the String in the JSON send in the request. This feels pretty wrong. I tried to do something smarter and failed badly.What I'd like to have:Givenclass MyEntity { String someString; int someInt; @Lob String clientData;}and an input{ someString: "The answer", someInt: 43, clientData: { x: [1, 1, 2, 3, 5, 8, 13], y: [1, 1, 2, 6, 24, 120], tonsOfComplicatedStuff: {stuff: stuff} }}store the clientData packed as JSON in a single column. Note that I don't want to write an adapter for MyEntity as there are many columns. I need an adapter for the single column. The column type needn't be a String (Serializable or anything else would do, as the server really doesn't care). 解决方案 Gson supports the @JsonAdapter annotation allowing to specify a JSON (de)serializer, type adapter, or even a type adapter factory. And the annotation looks like a good candidate to annotate the clientData field in MyEntity:final class MyEntity { String someString; int someInt; @Lob @JsonAdapter(PackedJsonTypeAdapterFactory.class) String clientData;}The type adapter factory may look as follows: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; } }}Note that this converter strategy is implemented as a type adapter factory, since this is the only way of accessing the Gson instance known to me, and JsonSerializer/JsonDeserializer do not seem to make good parsing via the serialization context. Another pitfall here is that this implementation is tree-based requiring JSON trees to be stored in memory completely. In theory, there could be a nice stream-oriented implementation like gson.fromJson(jsonReader) -> JsonReader or a JsonReader->Reader decorator to be redirected to a StringWriter for example, but I couldn't find any alternative for really long time.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);}The output: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 Don't know if it can play with Hibernate nicely, though.EditDespite JSON-packed strings are collected into the memory, streaming may be cheaper for various reasons and can save some memory. Another advantage of streaming is that such a JSON-packing type adapter does not need a type adapter factory anymore and Gson instances therefore keeping a JSON stream as-is, however still making some normalizations like {stuff:stuff} -> {"stuff":"stuff"}. For example:@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); } } }}This one parses and generates the same input and output respectively. The Longs.tryParse and Doubles.tryParse methods are taken from Google Guava. 这篇关于使用GSON和Hibernate存储任意数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!
10-30 09:47