Java中将JSON反序列化为泛型对象

将嵌套List的Map转换为Json应该都没什么问题,使用Gson和Jackson都能实现,在Gson中使用new Gson().toJson()方法,在Jackson中使用new ObjectMapper().writeValueAsString()即可。
将json转换为形如Map<String,List<Long>>的时候遇到了一点问题,虽然返回类型是Map<String,List<Long>>但是,Map的value的值却并不是List<Long>,而是Integer类型的,这里面显然是有问题的,查看Jackson的源码和Gson的源码发现
将json反序列化为对象确实有两个方法,一种适用于泛型对象,一种适用于非泛型的一般对象。

使用Gson

在gson中将json字符串转反序列化为对象有两个方法:


  /**
   * This method deserializes the specified Json into an object of the specified class. It is not
   * suitable to use if the specified class is a generic type since it will not have the generic
   * type information because of the Type Erasure feature of Java. Therefore, this method should not
   * be used if the desired type is a generic type. Note that this method works fine if the any of
   * the fields of the specified object are generics, just the object itself should not be a
   * generic type. For the cases when the object is of generic type, invoke
   * {@link #fromJson(String, Type)}. If you have the Json in a {@link Reader} instead of
   * a String, use {@link #fromJson(Reader, Class)} instead.
   *
   * @param <T> the type of the desired object
   * @param json the string from which the object is to be deserialized
   * @param classOfT the class of T
   * @return an object of type T from the string. Returns {@code null} if {@code json} is {@code null}.
   * @throws JsonSyntaxException if json is not a valid representation for an object of type
   * classOfT
   */
 public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
    Object object = fromJson(json, (Type) classOfT);
    return Primitives.wrap(classOfT).cast(object);
  }

  /**
   * This method deserializes the specified Json into an object of the specified type. This method
   * is useful if the specified object is a generic type. For non-generic objects, use
   * {@link #fromJson(String, Class)} instead. If you have the Json in a {@link Reader} instead of
   * a String, use {@link #fromJson(Reader, Type)} instead.
   *
   * @param <T> the type of the desired object
   * @param json the string from which the object is to be deserialized
   * @param typeOfT The specific genericized type of src. You can obtain this type by using the
   * {@link com.google.gson.reflect.TypeToken} class. For example, to get the type for
   * {@code Collection<Foo>}, you should use:
   * <pre>
   * Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType();
   * </pre>
   * @return an object of type T from the string. Returns {@code null} if {@code json} is {@code null}.
   * @throws JsonParseException if json is not a valid representation for an object of type typeOfT
   * @throws JsonSyntaxException if json is not a valid representation for an object of type
   */
  @SuppressWarnings("unchecked")
  public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
    if (json == null) {
      return null;
    }
    StringReader reader = new StringReader(json);
    T target = (T) fromJson(reader, typeOfT);
    return target;
  }

观察fromJson(String json, Class<T> classOfT)的注释:

It is not suitable to use if the specified class is a generic type since it will not have the generic type information because of the Type Erasure feature of Java

也就是说,由于Java泛型的擦除机制,这个方法不适用于传入泛型的类,比如Map<String,Long>,List<String>等,这个时候可以用T fromJson(String json, Type typeOfT)替代。

下面还有一段话:

Note that this method works fine if the any of the fields of the specified object are generics, just the object itself should not be a generic type

** 注意:** 如果对象不是泛型的,只是字段是泛型的话这个方法是可以使用的

刚开始不太理解这句话,后来想通了,也就是类定义上不能带有泛型比如 public interface Map<K,V> 这样的就不行,但是如果是下面这样的只有域上带有的泛型是可以:

static class JsonDemo{

        private List<Long> list;

        public List<Long> getList() {
            return list;
        }

        public void setList(List<Long> list) {
            this.list = list;
        }
    }

下面的fromJson(String json, Type typeOfT)就是专门提供给泛型类的对象使用的,如果你自己反序列化的对象带有泛型的话需要用这个方法。

使用Jackson

和gson一样,jackson也提供了两个方法,一个适用于普通的类,一个适用于泛型类,只不过jackson源码的注释没有Gson的丰富,从注释上看不出来,功能和Gson的一致。

  /**
     * Method to deserialize JSON content from given JSON content String.
     * 
     * @throws IOException if a low-level I/O problem (unexpected end-of-input,
     *   network error) occurs (passed through as-is without additional wrapping -- note
     *   that this is one case where {@link DeserializationFeature#WRAP_EXCEPTIONS}
     *   does NOT result in wrapping of exception even if enabled)
     * @throws JsonParseException if underlying input contains invalid content
     *    of type {@link JsonParser} supports (JSON for default case)
     * @throws JsonMappingException if the input JSON structure does not match structure
     *   expected for result type (or has other mismatch issues)
     */
   @SuppressWarnings("unchecked")
    public <T> T readValue(String content, Class<T> valueType)
        throws IOException, JsonParseException, JsonMappingException
    {
        return (T) _readMapAndClose(_jsonFactory.createParser(content), _typeFactory.constructType(valueType));
    } 

    /**
     * Method to deserialize JSON content from given JSON content String.
     * 
     * @throws IOException if a low-level I/O problem (unexpected end-of-input,
     *   network error) occurs (passed through as-is without additional wrapping -- note
     *   that this is one case where {@link DeserializationFeature#WRAP_EXCEPTIONS}
     *   does NOT result in wrapping of exception even if enabled)
     * @throws JsonParseException if underlying input contains invalid content
     *    of type {@link JsonParser} supports (JSON for default case)
     * @throws JsonMappingException if the input JSON structure does not match structure
     *   expected for result type (or has other mismatch issues)
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public <T> T readValue(String content, TypeReference valueTypeRef)
        throws IOException, JsonParseException, JsonMappingException
    {
        return (T) _readMapAndClose(_jsonFactory.createParser(content), _typeFactory.constructType(valueTypeRef));
    } 

简单实验

使用两种方式反序列一个json,使用Class来反序列化泛型类型的对象,在printType的时候会出现ClassCastException类型转换异常。

package org.xuan;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * Created by zhaohongxuan
 */
public class JsonTest {
    private static ObjectMapper mapper = new ObjectMapper();
    private static Gson gson = new Gson();
    public static void main(String[] args) throws IOException {
        Map<String, List<Long>> map = Maps.newHashMap();
        map.put("one", Arrays.asList(10001L, 10002L, 10003L, 10004L));
        map.put("two", Arrays.asList(20001L, 20002L, 20003L, 20004L));
        map.put("three", Arrays.asList(30001L, 30002L, 30003L, 30004L));
        map.put("four", Arrays.asList(40001L, 40002L, 40003L, 40004L));

        String json = new Gson().toJson(map);
        System.err.println("=======================错误示范=====================");
        //Gson
        Map<String, List<Long>> mapResult  = gson.fromJson(json,Map.class);
        System.out.println("通过Gson转换...");
//      printType(mapResult);
        System.out.println(mapResult);
        //Json
        Map<String, List<Long>> jsonMapResult = mapper.readValue(json,Map.class);
        System.out.println("通过Jackson转换...");
//      printType(jsonMapResult);

        System.out.println(jsonMapResult);
        System.out.println("=======================正确做法=====================");
        //Gson
        Map<String, List<Long>> mapResult1  = gson.fromJson(json,new TypeToken<Map<String, List<Long>>>(){}.getType());
        System.out.println("通过Gson转换...");
        printType(mapResult1);
        System.out.println(mapResult1);
        //Json
        ObjectMapper mapper = new ObjectMapper();
        Map<String, List<Long>> jsonMapResult1 = mapper.readValue(json,new TypeReference< Map<String,List<Long>>>() {});
        System.out.println("通过Jackson转换...");
        printType(jsonMapResult1);

        System.out.println(jsonMapResult1);

    }

    public static void printType(Map<String, List<Long>> map){
        for (Map.Entry<String, List<Long>> entry: map.entrySet()){
            System.out.println("key 类型:"+entry.getKey().getClass()+", value类型:"
            +entry.getValue().getClass()+", List中元素类型"+entry.getValue().get(0).getClass());
        }

    }
}


总 结

在Gson中:
如果使用fromJson(String json, Class<T> classOfT)来反序列化Map的话,不会造成编译错误,返回的类型就会变化,Long类型变成了Double类型,使用的时候就会出现异常,例如在遍历Map的entrySet的时候就会出现异常。

    java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Long

因此:

  1. 反序列化泛型对象如Map<K,V>等需要使用 fromJson(String json, Type typeOfT)
  2. 一般对象使用fromJson(String json, Class<T> classOfT)
    在Jackson中:
    如果使用T readValue(String content, Class<T> valueType)来反序列化Map的话,返回的类型就会由Long类型变成了Integer类型。
  3. 反序列化泛型对象如Map<K,V>等需要使用 T readValue(String content, TypeReference valueTypeRef)
  4. 一般对象使用T readValue(String content, Class<T> valueType)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 1.概述2.Gson的目标3.Gson的性能和扩展性4.Gson的使用者5.如何使用Gson 通过Maven来使用...
    人失格阅读 14,192评论 2 18
  • 为了更好的学习Gson,特将Gson User Guide翻译如下。由于本人英文水平有限,如有错误,还请指正,谢谢...
    WeberLisper阅读 6,699评论 0 6
  • 为了这通电话,我准备了两天,或者说两年。 两年前,就跟你们失去了联系,从此开始了对你的苦苦追寻,我们甚至还去到了你...
    五月成长笔记阅读 189评论 0 0
  • 今天不知怎么回事,总有心乱如麻的感觉,准确的说 我感到大厦将傾 我感到莫大的压力。 秋天很认真地到了。 ...
    南无NAMO阅读 109评论 0 0
  • 前几天过完年返程的路途中,有这样一个故事。一对夫妻,30多岁左右,两人只有一张有座位的票,还有一张站票。理所当然的...
    爱思考的安妮阅读 326评论 0 0