我有一些
JSON进来(我没有任何控制或能力来改变JSON中的结构和/或命名…这个问题中要记住这一点很重要),它具有类似于此的“扁平”结构:
{ "name": "...","email": "...","Box_background_color": "...","Box_border_color": "...","Box_text_color": "...",... }
现在,我可以创建一个简单的对象来保持一切平坦,如下所示:
public class Settings { @SerializedName("name") private String _name; @SerializedName("email") private String _emailAddress; @SerializedName("Box_background_color") private String _BoxBackgroundColor; @SerializedName("Box_border_color") private String _BoxBorderColor; @SerializedName("Box_text_color") private String _BoxTextColor; ... }
但是,我希望与盒子设置相关的所有内容都在它自己的类中(BoxSettings).这更像我想要的:
public class Settings { @SerializedName("name") private String _name; @SerializedName("email") private String _emailAddress; private BoxSettings _BoxSettings ... } public class BoxSettings { @SerializedName("Box_background_color") private String _BoxBackgroundColor; @SerializedName("Box_border_color") private String _BoxBorderColor; @SerializedName("Box_text_color") private String _BoxTextColor; ... }
我知道如果JSON的结构使得盒子设置是嵌套的,那么很容易实现我想要的,但是,我没有能力改变JSON的结构,所以请不要建议(如果可以,我会这样做).
我的问题是:创建一个完整的TypeAdapter是实现我想要的唯一方法,还是我仍然可以通过注释完成大部分工作?如果不是唯一的方法,我怎么能在不改变JSON的情况下完成这个呢?
以下是“创建整个TypeAdapter”的含义示例:
public class SettingsTypeAdapter implements JsonDeserializer<Settings>,JsonSerializer<Settings> { @Override public JsonElement serialize(Settings src,Type typeOfSrc,JsonSerializationContext context) { // Add _name // Add _emailAddress // Add BoxSettings._BoxBackgroundColor // Add BoxSettings._BoxBorderColor // Add BoxSettings._BoxTextColor return jsonElement; } @Override public Settings deserialize(JsonElement json,Type typeOfT,JsonDeserializationContext context) throws JsonParseException { // Read _name // Read _emailAddress // Read BoxSettings._BoxBackgroundColor // Read BoxSettings._BoxBorderColor // Read BoxSettings._BoxTextColor return settings; } }
解决方法
TypeAdapter不是唯一的方法,但在这种情况下,这是最好的方法,因为您可以将适配器与Gson实例(或您正在使用的任何库)相关联,并在那里拥有所有映射代码.
另一种方法是使用JAVA反射.我以前在我的项目中使用了以下代码的版本,但从未使用过JSON,从不使用嵌套对象(大多数时候没有其他选择,或者我想将sql结果集映射到Java对象而不调用resultSet.get … 很多时间).
这将适用于这种情况.
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.json.JSONObject; public class Main { public static void main(String[] args) { try { String json = "{\"name\": \"test name\",\"email\": \"email@email.com\",\"Box_background_color\": \"red\",\"Box_border_color\": \"orange\",\"Box_text_color\": \"white\",\"test3_var2\":3}"; JSONObject jsonObject = new JSONObject(json); System.out.println(jsonObject); System.out.println(); /* * need to parse JSON into a map of String,Object */ Map<String,Object> mapAll = new HashMap<String,Object>(); Iterator<String> iter = jsonObject.keys(); while (iter.hasNext()) { String key = (String) iter.next(); Object value = jsonObject.get(key); mapAll.put(key,value); System.out.println(key + "::::" + value); } System.out.println(); /* * use the mapper to generate the objects */ MyMapper<TestClass1> myMapper = new MyMapper<TestClass1>(); TestClass1 result = myMapper.mapToObject(mapAll,TestClass1.class); System.out.println(result); } catch (Exception e) { e.printStackTrace(); } } } class MyMapper<T> { @SuppressWarnings("unchecked") public T mapToObject(Map<String,Object> flatStructure,Class<T> objectClass) { T result = null; Field[] fields = null; try { // new base object result = objectClass.newInstance(); // get all of its fields fields = objectClass.getDeclaredFields(); for (Field field : fields) { // normal variable if (field.isAnnotationPresent(MyColumn.class)) { String variableKey = field.getAnnotation(MyColumn.class).variableKey(); setJavaFieldValue(result,field.getName(),flatStructure.get(variableKey)); } // variable that is an object and itself has to be mapped else if (field.isAnnotationPresent(MyInnerColumn.class)) { String startsWith = field.getAnnotation(MyInnerColumn.class).startsWith(); // reduce the map to only have attributes that are related to this field Map<String,Object> reducedMap = reduceMap(startsWith,flatStructure); // make sure that there are attributes for the inner object if (reducedMap != null) { // map the inner object MyMapper<T> myMapper = new MyMapper<T>(); T t2 = myMapper.mapToObject(reducedMap,(Class<T>) field.getType()); // set the mapped object to the base objecct setJavaFieldValue(result,t2); } } else { // no annotation on the field so ignored } } } catch (Exception e) { e.printStackTrace(); } return result; } private Map<String,Object> reduceMap(String startsWith,Map<String,Object> mapToReduce) { Map<String,Object> result = new HashMap<String,Object>(); for (Map.Entry<String,Object> entry : mapToReduce.entrySet()) { if (entry.getKey().toLowerCase().startsWith(startsWith.toLowerCase())) { result.put(entry.getKey(),entry.getValue()); } } return result.size() == 0 ? null : result; } private void setJavaFieldValue(Object object,String fieldName,Object fieldValue) { try { Field field = object.getClass().getDeclaredField(fieldName); boolean fieldAccess = field.isAccessible(); // make the field accessible field.setAccessible(true); field.set(object,fieldValue); // put it back to the way it was field.setAccessible(fieldAccess); } catch (Exception e) { e.printStackTrace(); } } } /* * Annotation for a regular variable / field */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface MyColumn { // the variable's JSON key String variableKey() default ""; } /* * Annotation for an inner / nested variable / field */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface MyInnerColumn { /* * JSON keys that start with this string will be * associated with this nested field */ String startsWith() default ""; } class TestClass1 { @MyColumn(variableKey = "name") private String _name; @MyColumn(variableKey = "email") private String _emailAddress; @MyInnerColumn(startsWith = "Box_") private TestClass2 innerClass; @MyInnerColumn(startsWith = "test3_") private TestClass3 innerClass2; @Override public String toString() { return "TestClass1 [_name=" + _name + ",_emailAddress=" + _emailAddress + ",innerClass=" + innerClass + ",innerClass2=" + innerClass2 + "]"; } } class TestClass2 { @MyColumn(variableKey = "Box_background_color") private String _BoxBackgroundColor; @MyColumn(variableKey = "Box_border_color") private String _BoxBorderColor; @MyColumn(variableKey = "Box_text_color") private String _BoxTextColor; @Override public String toString() { return "TestClass2 [_BoxBackgroundColor=" + _BoxBackgroundColor + ",_BoxBorderColor=" + _BoxBorderColor + ",_BoxTextColor=" + _BoxTextColor + "]"; } } class TestClass3 { @MyColumn(variableKey = "test3_var1") private String _test3Var1; @MyColumn(variableKey = "test3_var2") private int _test3Var2; @Override public String toString() { return "TestClass3 [_test3Var1=" + _test3Var1 + ",_test3Var2=" + _test3Var2 + "]"; } }
产量
{"Box_background_color":"red","Box_text_color":"white","test3_var2":3,"name":"test name","email":"email@email.com","Box_border_color":"orange"} Box_background_color::::red Box_text_color::::white test3_var2::::3 name::::test name email::::email@email.com Box_border_color::::orange TestClass1 [_name=test name,_emailAddress=email@email.com,innerClass=TestClass2 [_BoxBackgroundColor=red,_BoxBorderColor=orange,_BoxTextColor=white],innerClass2=TestClass3 [_test3Var1=null,_test3Var2=3]]