我想保留一些仅与客户端相关的数据。我想故意忽略数据库规范化,因为数据在服务器端非常无用。
我可以通过让客户端将数据转换为JSON并将String包含在请求中发送的JSON中来轻松完成此操作。但是,我希望有一个更干净,更优雅的解决方案。
我想要的是:
给定
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}
}
}
将打包为JSON的
clientData
存储在单个列中。请注意,我不想为MyEntity
编写适配器,因为有很多列。我需要一个用于单列的适配器。列类型不必是字符串(Serializable
或其他任何可以做的,因为服务器实际上不在乎)。 最佳答案
Gson支持@JsonAdapter
注释,允许指定JSON(反)序列化器,类型适配器,甚至类型适配器工厂。注释似乎是注释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) -> JsonReader
或JsonReader
-> 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}
-> {"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.tryParse
和Doubles.tryParse
方法取自Google Guava。