使用GSon将JSon转换为多个未知java对象类型
我有一个netty解码器,它使用GSon将来自web客户端的JSon转换为适当的java对象。 要求是: 客户端可能会发送不相关的类,如类A、类B、类C等,但我希望使用管道中相同的单例解码器实例来进行转换(因为我使用spring来配置它)。我面临的问题是我需要事先知道使用GSon将JSon转换为多个未知java对象类型,java,json,gson,netty,Java,Json,Gson,Netty,我有一个netty解码器,它使用GSon将来自web客户端的JSon转换为适当的java对象。 要求是: 客户端可能会发送不相关的类,如类A、类B、类C等,但我希望使用管道中相同的单例解码器实例来进行转换(因为我使用spring来配置它)。我面临的问题是我需要事先知道类对象 public Object decode() { gson.fromJson(jsonString, A.class); } 这无法解码B或C。我的库的用户现在需要为每个类编写单独的解码器,而不是稍后的转换。我能看
类
对象
public Object decode()
{
gson.fromJson(jsonString, A.class);
}
这无法解码B或C。我的库的用户现在需要为每个类编写单独的解码器,而不是稍后的转换。我能看到的唯一方法是在web客户端的JSon字符串中传递类的字符串名,比如“org.example.C”,在解码器中解析出来,然后使用
class.forName
获取类。有更好的方法吗?假设您有以下两种可能的JSON响应:
{
"classA": {"foo": "fooValue"}
}
or
{
"classB": {"bar": "barValue"}
}
可以创建如下所示的类结构:
public class Response {
private A classA;
private B classB;
//more possible responses...
//getters and setters...
}
public class A {
private String foo;
//getters and setters...
}
public class B {
private String bar;
//getters and setters...
}
然后,您可以使用以下方法解析任何可能的JSON响应:
Response response = gson.fromJson(jsonString, Response.class);
Gson将忽略所有与类结构中的任何属性都不对应的JSON字段,因此您可以调整单个类来解析不同的响应
然后,您可以检查哪些属性
classA
,classB
。。。不是null
,您将知道收到了哪个响应。GSon必须知道与json字符串匹配的类。如果不想用fromJson()提供它,实际上可以在Json中指定它。一种方法是定义接口并在其上绑定适配器
比如:
class A implements MyInterface {
// ...
}
public Object decode()
{
Gson gson = builder.registerTypeAdapter(MyInterface.class, new MyInterfaceAdapter());
MyInterface a = gson.fromJson(jsonString, MyInterface.class);
}
适配器可以类似于:
public final class MYInterfaceAdapter implements JsonDeserializer<MyInterface>, JsonSerializer<MyInterface> {
private static final String PROP_NAME = "myClass";
@Override
public MyInterface deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
String classPath = json.getAsJsonObject().getAsJsonPrimitive(PROP_NAME).getAsString();
Class<MyInterface> cls = (Class<MyInterface>) Class.forName(classPath);
return (MyInterface) context.deserialize(json, cls);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
@Override
public JsonElement serialize(MyInterface src, Type typeOfSrc, JsonSerializationContext context) {
// note : won't work, you must delegate this
JsonObject jo = context.serialize(src).getAsJsonObject();
String classPath = src.getClass().getName();
jo.add(PROP_NAME, new JsonPrimitive(classPath));
return jo;
}
}
public final类MYInterfaceAdapter实现JsonDeserializer、JsonSerializer{
私有静态最终字符串PROP_NAME=“myClass”;
@凌驾
公共MyInterface反序列化(JsonElement json,类型typeOfT,JsonDeserializationContext)引发JsonParseException{
试一试{
字符串classPath=json.getAsJsonObject().getAsJsonPrimitive(PROP_NAME).getAsString();
Class cls=(Class)Class.forName(classPath);
返回(MyInterface)上下文。反序列化(json、cls);
}catch(classnotfounde异常){
e、 printStackTrace();
}
返回null;
}
@凌驾
公共JsonElement序列化(MyInterface src,Type typeOfSrc,JsonSerializationContext){
//注意:不起作用,您必须委派此任务
JsonObject jo=context.serialize(src.getAsJsonObject();
字符串classPath=src.getClass().getName();
add(PROP_NAME,newjsonprimitive(classPath));
返回jo;
}
}
创建模型类
public class MyModel {
private String errorId;
public String getErrorId() {
return errorId;
}
public void setErrorId(String errorId) {
this.errorId = errorId;
}
}
创建子类
public class SubClass extends MyModel {
private String subString;
public String getSubString() {
return subString;
}
public void setSubString(String subString) {
this.subString = subString;
}
}
调用parseGson方法
parseGson(subClass);
带有对象类的gson解析方法
public void parseGson(Object object){
object = gson.fromJson(response.toString(), object.getClass());
SubClass subclass = (SubClass)object;
}
您可以设置强制转换为myModel的全局变量
((MyModel)object).setErrorId(response.getString("errorid"));
不确定这是否是您想要的,但通过修改RuntimeTypeAdapterFactory类,我创建了一个基于Json源中条件的子类化系统。 RuntimeTypeAdapterFactory.class:
/*
* Copyright (C) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.typeadapters;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* Adapts values whose runtime type may differ from their declaration type. This
* is necessary when a field's type is not the same type that GSON should create
* when deserializing that field. For example, consider these types:
* <pre> {@code
* abstract class Shape {
* int x;
* int y;
* }
* class Circle extends Shape {
* int radius;
* }
* class Rectangle extends Shape {
* int width;
* int height;
* }
* class Diamond extends Shape {
* int width;
* int height;
* }
* class Drawing {
* Shape bottomShape;
* Shape topShape;
* }
* }</pre>
* <p>Without additional type information, the serialized JSON is ambiguous. Is
* the bottom shape in this drawing a rectangle or a diamond? <pre> {@code
* {
* "bottomShape": {
* "width": 10,
* "height": 5,
* "x": 0,
* "y": 0
* },
* "topShape": {
* "radius": 2,
* "x": 4,
* "y": 1
* }
* }}</pre>
* This class addresses this problem by adding type information to the
* serialized JSON and honoring that type information when the JSON is
* deserialized: <pre> {@code
* {
* "bottomShape": {
* "type": "Diamond",
* "width": 10,
* "height": 5,
* "x": 0,
* "y": 0
* },
* "topShape": {
* "type": "Circle",
* "radius": 2,
* "x": 4,
* "y": 1
* }
* }}</pre>
* Both the type field name ({@code "type"}) and the type labels ({@code
* "Rectangle"}) are configurable.
*
* <h3>Registering Types</h3>
* Create a {@code RuntimeTypeAdapter} by passing the base type and type field
* name to the {@link #of} factory method. If you don't supply an explicit type
* field name, {@code "type"} will be used. <pre> {@code
* RuntimeTypeAdapter<Shape> shapeAdapter
* = RuntimeTypeAdapter.of(Shape.class, "type");
* }</pre>
* Next register all of your subtypes. Every subtype must be explicitly
* registered. This protects your application from injection attacks. If you
* don't supply an explicit type label, the type's simple name will be used.
* <pre> {@code
* shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");
* shapeAdapter.registerSubtype(Circle.class, "Circle");
* shapeAdapter.registerSubtype(Diamond.class, "Diamond");
* }</pre>
* Finally, register the type adapter in your application's GSON builder:
* <pre> {@code
* Gson gson = new GsonBuilder()
* .registerTypeAdapter(Shape.class, shapeAdapter)
* .create();
* }</pre>
* Like {@code GsonBuilder}, this API supports chaining: <pre> {@code
* RuntimeTypeAdapter<Shape> shapeAdapter = RuntimeTypeAdapterFactory.of(Shape.class)
* .registerSubtype(Rectangle.class)
* .registerSubtype(Circle.class)
* .registerSubtype(Diamond.class);
* }</pre>
*/
public final class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
private final Class<?> baseType;
private final RuntimeTypeAdapterPredicate predicate;
private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>();
private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>();
private RuntimeTypeAdapterFactory(Class<?> baseType, RuntimeTypeAdapterPredicate predicate) {
if (predicate == null || baseType == null) {
throw new NullPointerException();
}
this.baseType = baseType;
this.predicate = predicate;
}
/**
* Creates a new runtime type adapter using for {@code baseType} using {@code
* typeFieldName} as the type field name. Type field names are case sensitive.
*/
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, RuntimeTypeAdapterPredicate predicate) {
return new RuntimeTypeAdapterFactory<T>(baseType, predicate);
}
/**
* Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
* the type field name.
*/
public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
return new RuntimeTypeAdapterFactory<T>(baseType, null);
}
/**
* Registers {@code type} identified by {@code label}. Labels are case
* sensitive.
*
* @throws IllegalArgumentException if either {@code type} or {@code label}
* have already been registered on this type adapter.
*/
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
if (type == null || label == null) {
throw new NullPointerException();
}
if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
throw new IllegalArgumentException("types and labels must be unique");
}
labelToSubtype.put(label, type);
subtypeToLabel.put(type, label);
return this;
}
/**
* Registers {@code type} identified by its {@link Class#getSimpleName simple
* name}. Labels are case sensitive.
*
* @throws IllegalArgumentException if either {@code type} or its simple name
* have already been registered on this type adapter.
*/
public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
return registerSubtype(type, type.getSimpleName());
}
public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
if (type.getRawType() != baseType) {
return null;
}
final Map<String, TypeAdapter<?>> labelToDelegate
= new LinkedHashMap<String, TypeAdapter<?>>();
final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate
= new LinkedHashMap<Class<?>, TypeAdapter<?>>();
for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
labelToDelegate.put(entry.getKey(), delegate);
subtypeToDelegate.put(entry.getValue(), delegate);
}
return new TypeAdapter<R>() {
@Override public R read(JsonReader in) throws IOException {
JsonElement jsonElement = Streams.parse(in);
String label = predicate.process(jsonElement);
@SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
if (delegate == null) {
throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
+ label + "; did you forget to register a subtype?");
}
return delegate.fromJsonTree(jsonElement);
}
@Override public void write(JsonWriter out, R value) throws IOException { // Unimplemented as we don't use write.
/*Class<?> srcType = value.getClass();
String label = subtypeToLabel.get(srcType);
@SuppressWarnings("unchecked") // registration requires that subtype extends T
TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
if (delegate == null) {
throw new JsonParseException("cannot serialize " + srcType.getName()
+ "; did you forget to register a subtype?");
}
JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
if (jsonObject.has(typeFieldName)) {
throw new JsonParseException("cannot serialize " + srcType.getName()
+ " because it already defines a field named " + typeFieldName);
}
JsonObject clone = new JsonObject();
clone.add(typeFieldName, new JsonPrimitive(label));
for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
clone.add(e.getKey(), e.getValue());
}*/
Streams.write(null, out);
}
};
}
}
示例(摘自我目前正在从事的一个项目):
ItemTypePredicate.class:
用法:
RuntimeTypeAdapterFactory itemAdapter=RuntimeTypeAdapterFactory.of(Item.class,new ItemTypePredicate())
.registerSubtype(Currency.class)
.注册表子类型(设备.类别)
.registerSubtype(Gem.class)
.registerSubtype(映射类);
Gson Gson=new GsonBuilder()
.enableComplexMapKeySerialization()
.registerTypeAdapterFactory(itemAdapter).create();
层次结构基类是Item。货币、设备、Gem和Map都扩展了这一功能。如果这是一个通用组件,请考虑您希望用户如何配置它。明智的违约也值得考虑。有多个解码器实例,每个类一个,有意义吗?这就是我目前的做法,每个类类型一个实例。问题是我的库的用户现在需要做一些内部细节,比如解码器和编码器,并在每次添加新类时在spring中配置它们。我不想让他们这样做,而是做一个简单的转换。这是为一个发布给用户的库->所以我真的不知道他们的类。所以我无法填写响应类。@Abe,所以您必须解析大量JSON响应,而您根本不知道它们的外观如何?这真是一项艰苦的工作!实际上,客户机可以发送类名,因为他们知道要发送什么,所以这不是一个丢失的原因。这里的问题是,类B将不会实现
MyInterface
。这些不是逻辑链接的类。但我可能需要让lib的用户实现一个标记接口来让代码正常工作。要点是JSon必须提供要实例化的类。GSon并不是神奇的,它无法为您猜测:)因为1.7GsonBuilder
builder有一种可能更适合这种情况的方法。这是绝对错误的。Gson可以将任何有效的JSON反序列化到如下语法树中:Gson.fromJson(JSON,JsonElement.class)
。您可以根据需要解释由JSON数组(列表)、JSON对象(映射)和JSON原语(数字、字符串、布尔值、null)组成的语法树。不要使用此解决方案。让攻击者自由选择对象的类型会使应用程序容易受到各种反序列化攻击。根据包含的库的不同,可能会导致DoS、内存损坏或删除任意文件,甚至更多。例如,这几乎就是我想要的,但我的基本要求是识别不相关的类,我认为这是不可能的。
package com.google.gson.typeadapters;
import com.google.gson.JsonElement;
/**
* Created by Johan on 2014-02-13.
*/
public abstract class RuntimeTypeAdapterPredicate {
public abstract String process(JsonElement element);
}
package org.libpoe.serial;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.typeadapters.RuntimeTypeAdapterPredicate;
/**
* Created by Johan on 2014-02-13.
*/
public class ItemTypePredicate extends RuntimeTypeAdapterPredicate {
@Override
public String process(JsonElement element) {
JsonObject obj = element.getAsJsonObject();
int frameType = obj.get("frameType").getAsInt();
switch(frameType) {
case 4: return "Gem";
case 5: return "Currency";
}
if (obj.get("typeLine").getAsString().contains("Map")
&& obj.get("descrText").getAsString() != null
&& obj.get("descrText").getAsString().contains("Travel to this Map")) {
return "Map";
}
return "Equipment";
}
}
RuntimeTypeAdapterFactory<Item> itemAdapter = RuntimeTypeAdapterFactory.of(Item.class, new ItemTypePredicate())
.registerSubtype(Currency.class)
.registerSubtype(Equipment.class)
.registerSubtype(Gem.class)
.registerSubtype(Map.class);
Gson gson = new GsonBuilder()
.enableComplexMapKeySerialization()
.registerTypeAdapterFactory(itemAdapter).create();