使用GSon从JSon转换为多个未知的Java对象类型

我有一个netty解码器,它使用GSon将来自Web客户端的JSon转换为适当的java对象。 要求是:客户端可以发送不相关的类,A类,B类,C类等,但我想在管道中使用相同的单例解码器实例进行转换(因为我使用spring进行配置)。 我面临的问题是我需要事先知道class对象。

 public Object decode() { gson.fromJson(jsonString, A.class); } 

这不能解码B或C.我的库的用户现在需要为每个类编写单独的解码器,而不是后来的演员阵容。 我可以看到这样做的唯一方法是从Web客户端传递JSon字符串中的类名称“org.example.C”,在解码器中解析它然后使用Class.forName来获取类。 有一个更好的方法吗?

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, JsonSerializer { 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 cls = (Class) 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; } } 

假设您有以下两种可能的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字段,因此您可以调整单个类来解析不同的响应…

然后,您可以检查classAclassB ,…中的哪些属性不为null ,您将知道您收到了哪个响应。

不确定这是否是您要求的,但是通过修改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: * 
 {@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; * } * }

*

Without additional type information, the serialized JSON is ambiguous. Is * the bottom shape in this drawing a rectangle or a diamond?

 {@code * { * "bottomShape": { * "width": 10, * "height": 5, * "x": 0, * "y": 0 * }, * "topShape": { * "radius": 2, * "x": 4, * "y": 1 * } * }}

* This class addresses this problem by adding type information to the * serialized JSON and honoring that type information when the JSON is * deserialized:

 {@code * { * "bottomShape": { * "type": "Diamond", * "width": 10, * "height": 5, * "x": 0, * "y": 0 * }, * "topShape": { * "type": "Circle", * "radius": 2, * "x": 4, * "y": 1 * } * }}

* Both the type field name ({@code "type"}) and the type labels ({@code * "Rectangle"}) are configurable. * *

Registering Types

* 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.

 {@code * RuntimeTypeAdapter shapeAdapter * = RuntimeTypeAdapter.of(Shape.class, "type"); * }

* 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. *

 {@code * shapeAdapter.registerSubtype(Rectangle.class, "Rectangle"); * shapeAdapter.registerSubtype(Circle.class, "Circle"); * shapeAdapter.registerSubtype(Diamond.class, "Diamond"); * }

* Finally, register the type adapter in your application's GSON builder: *

 {@code * Gson gson = new GsonBuilder() * .registerTypeAdapter(Shape.class, shapeAdapter) * .create(); * }

* Like {@code GsonBuilder}, this API supports chaining:

 {@code * RuntimeTypeAdapter shapeAdapter = RuntimeTypeAdapterFactory.of(Shape.class) * .registerSubtype(Rectangle.class) * .registerSubtype(Circle.class) * .registerSubtype(Diamond.class); * }

*/ public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { private final Class baseType; private final RuntimeTypeAdapterPredicate predicate; private final Map> labelToSubtype = new LinkedHashMap>(); private final Map, String> subtypeToLabel = new LinkedHashMap, 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 RuntimeTypeAdapterFactory of(Class baseType, RuntimeTypeAdapterPredicate predicate) { return new RuntimeTypeAdapterFactory(baseType, predicate); } /** * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as * the type field name. */ public static RuntimeTypeAdapterFactory of(Class baseType) { return new RuntimeTypeAdapterFactory(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 registerSubtype(Class 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 registerSubtype(Class type) { return registerSubtype(type, type.getSimpleName()); } public TypeAdapter create(Gson gson, TypeToken type) { if (type.getRawType() != baseType) { return null; } final Map> labelToDelegate = new LinkedHashMap>(); final Map, TypeAdapter> subtypeToDelegate = new LinkedHashMap, TypeAdapter>(); for (Map.Entry> 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() { @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 delegate = (TypeAdapter) 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 delegate = (TypeAdapter) 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 e : jsonObject.entrySet()) { clone.add(e.getKey(), e.getValue()); }*/ Streams.write(null, out); } }; } }

RuntimeTypeAdapterPredicate.class:

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

示例(取自我目前正在进行的项目):

ItemTypePredicate.class:

 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 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(); 

层次基类是Item。 货币,设备,gem和地图都延伸了这一点。

创建模型类,

 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 parse方法

  public void parseGson(Object object){ object = gson.fromJson(response.toString(), object.getClass()); SubClass subclass = (SubClass)object; } 

您可以设置强制转换为myModel的全局变量

 ((MyModel)object).setErrorId(response.getString("errorid"));