在項目中遇到了一個JSON的坑。記錄下。
直接上代碼:
import java.util.ArrayList; import com.alibaba.fastjson.JSON; public class MyList<E> extends ArrayList<E> { private int size; private String specialName; public MyList(){ super(0); } public MyList(int size){ super(0); this.size = size; } public MyList(String specialName){ super(0); this.specialName = specialName; } public MyList(int size, String specialName){ super(0); this.size = size; this.specialName = specialName; } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public String getSpecialName() { return specialName; } public void setSpecialName(String specialName) { this.specialName = specialName; } public static void main(String[] args){ MyList<Integer> list = new MyList<Integer>(); list.add(1); list.add(2); list.add(3); list.add(4); list.setSpecialName("just a test"); list.setSize(4); System.out.println(JSON.toJSON(list)); System.out.println(JSON.toJSONString(list)); } }
輸出的結果為:
[1,2,3,4]
[1,2,3,4]
但是我們期望的結果卻是類似於下面這樣的結果:
{size:4, specialName:"just a test", [1,2,3,4]}
那么是哪里出問題了呢?導致 MyList的 size 屬性和 specialName 在JSON格式化時,被丟棄了呢?
下面在看一個例子:
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.alibaba.fastjson.JSON; public class MyList2<E> { private int size; private String specialName; private List<E> list; public MyList2(){ } public int getSize() { return size; } public void setSize(int size) { this.size = size; } public String getSpecialName() { return specialName; } public void setSpecialName(String specialName) { this.specialName = specialName; } public List<E> getList() { return list; } public void setList(List<E> list) { this.list = list; } public static void main(String[] args){ MyList2<Integer> myList = new MyList2<Integer>(); ArrayList<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.add(4); myList.setSpecialName("just a test"); myList.setSize(4); myList.setList(list); System.out.println(JSON.toJSON(myList)); System.out.println(JSON.toJSONString(myList)); System.out.println("----------------"); Map<String, Object> map = new HashMap<>(); map.put("size", 4); map.put("specialName", "just a test"); map.put("list", list); map.put("myList", myList); System.out.println(JSON.toJSON(map)); } }
輸出的結果為:
{"list":[1,2,3,4],"size":4,"specialName":"just a test"} {"list":[1,2,3,4],"size":4,"specialName":"just a test"} ---------------- {"specialName":"just a test","size":4,"list":[1,2,3,4],"myList":{"list":[1,2,3,4],"size":4,"specialName":"just a test"}}
結果完全正確。
到這里,我們應該可以知道什么原因了。
上面第一段代碼我們期望的結果:
{size:4, specialName:"just a test", [1,2,3,4]}
但是想一想,這個格式有什么問題嗎?仔細想一想。
FK,這個格式是完全錯誤的!他是不合法的。試想一下,JSON格式說白了,它是 Javascript 的一個子集,合法的json對象它是 javascript 中的對象,但是:
{size:4, specialName:"just a test", [1,2,3,4]}
他是一個合法的javascript對象嗎????
顯然,它不是的。因為其中的 [1,2,3,4] ,我們無法訪問,它沒有對應到一個屬性。所以我們無法訪問它。所以他不是一個合法的javascript對象,顯然也就更加不是一個合法的json對象了。所以第一個例子,輸出的結果:[1,2,3,4] 其實是可以接受的。
是我們自己想要將一個不適合轉化成JSON格式的Java對象強制轉化成一個json格式,所以也就難免出現意料之外的情況了。那么 [1,2,3,4] 這個結果是如何來的呢。經過調試,其中的原因是,因為MyList繼承自ArrayList,所以在JSON格式化時,就是將其作為了一個List或者說數組來處理的,我們看下相關源碼:
public static final Object toJSON(Object javaObject) { return toJSON(javaObject, ParserConfig.getGlobalInstance()); } @SuppressWarnings("unchecked") public static final Object toJSON(Object javaObject, ParserConfig mapping) { if (javaObject == null) { return null; } if (javaObject instanceof JSON) { return (JSON) javaObject; } if (javaObject instanceof Map) { Map<Object, Object> map = (Map<Object, Object>) javaObject; JSONObject json = new JSONObject(map.size()); for (Map.Entry<Object, Object> entry : map.entrySet()) { Object key = entry.getKey(); String jsonKey = TypeUtils.castToString(key); Object jsonValue = toJSON(entry.getValue()); json.put(jsonKey, jsonValue); } return json; } if (javaObject instanceof Collection) { Collection<Object> collection = (Collection<Object>) javaObject; JSONArray array = new JSONArray(collection.size()); for (Object item : collection) { Object jsonValue = toJSON(item); array.add(jsonValue); } return array; }
我們的MyList的對象,在 if (javaObject instanceof Collection) 處為true,所以被當做了JSONArray 來處理的,每次處理JSONArray 中的一項,所以顯然就不會處理到 size屬性和specialName屬性了,所以這兩個屬性被拋棄了。這是 JSON.toJSON() 的處理過程。
下面看下 JSON.toJSONString(),其實也是一樣,當成了數組或者說List來處理了:
public ObjectSerializer getObjectWriter(Class<?> clazz) { ObjectSerializer writer = get(clazz); if (writer == null) { try { final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); for (Object o : ServiceLoader.load(AutowiredObjectSerializer.class, classLoader)) { if (!(o instanceof AutowiredObjectSerializer)) { continue; } AutowiredObjectSerializer autowired = (AutowiredObjectSerializer) o; for (Type forType : autowired.getAutowiredFor()) { put(forType, autowired); } } } catch (ClassCastException ex) { // skip } writer = get(clazz); } if (writer == null) { final ClassLoader classLoader = JSON.class.getClassLoader(); if (classLoader != Thread.currentThread().getContextClassLoader()) { try { for (Object o : ServiceLoader.load(AutowiredObjectSerializer.class, classLoader)) { if (!(o instanceof AutowiredObjectSerializer)) { continue; } AutowiredObjectSerializer autowired = (AutowiredObjectSerializer) o; for (Type forType : autowired.getAutowiredFor()) { put(forType, autowired); } } } catch (ClassCastException ex) { // skip } writer = get(clazz); } } if (writer == null) { if (Map.class.isAssignableFrom(clazz)) { put(clazz, MapSerializer.instance); } else if (List.class.isAssignableFrom(clazz)) { put(clazz, ListSerializer.instance);
上面的代碼 else if (List.class.isAssignableFrom(clazz)) 處為 true,所以其實是使用了 ListSerializer.instance 來處理了 MyList 的對象的,也就是當做了java.util.List 來處理的,所以自然就拋棄了 size屬性和specialName屬性。和上面的原理是一樣的。
總結下:
1)MyList這樣繼承自List的對象,並且有擴展屬性的,它在json格式中是不能十分恰當的表示的,它會被當成List來處理,從而拋棄其它非List的屬性。
2)避免方法就是使用組合代替繼承,或者使用Map,java.util.Map 天生就和json格式是最合適的,其實javascript中的對象,其實在某種程度上就是一個Map而已,屬性名就是map中的key, 屬性的值就是map的value。
3)JSON格式介紹參見:http://www.cnblogs.com/digdeep/p/4572662.html