当Json序列化与反序列遇到Map

背景

软件开发从数据的角度会经历三个阶段:获取数据,处理数据,展示数据

获取数据有如下方式:

  1. 从数据库获取,
  2. 从网络请求获取,
  3. 从本地生成业务逻辑的数据。

App客户端上,数据最初来源于后台接口,http请求获取到json字符串,json反序列化生成实体类;在这个反序列化过程中可以生成基本数据类型,JavaBean,数组,集合。集合里有List和Map,平时我们遇到反序列化情况是Map<String,Object>,如果需要序列化成Map<Object,Object>,要怎么处理呢?

Json格式的表达,是以Key:Value的形式,Key只能是字符串,Value可以是对象或者数据,Map\<Object,Object>中Map的键是对象,这要怎么用json表现?

常用的Json解析库有Gson,Jackson, Fastjson。阿里的Fastjson号称解析速度最快,Jackson解析速度比Gson快,常用在服务器端。谷歌的Gson解析稳定,常在App端使用,在中小数据量解析上和Jackson性能相近。我们来看看这三个库解析的结果

正文

Gson与Map\<Object,Object>

数据准备,生成Map集合

private LinkedHashMap<CategoryEntity, List<CategoryEntity>> createMap() {
    LinkedHashMap<CategoryEntity, List<CategoryEntity>> categoryMap = new LinkedHashMap<>();

    List<CategoryEntity> firstCategoryList = new ArrayList<>();
    firstCategoryList.add(new CategoryEntity("201", "全部分类", true));
    categoryMap.put(new CategoryEntity("101", "全部分类", true), firstCategoryList);

    List<CategoryEntity> secondCategoryList = new ArrayList<>();
    secondCategoryList.add(new CategoryEntity("211", "遥控赛车", false));
    secondCategoryList.add(new CategoryEntity("212", "乐高积木", false));
    secondCategoryList.add(new CategoryEntity("213", "泰迪小熊", false));
    categoryMap.put(new CategoryEntity("111", "儿童玩具", false), secondCategoryList);

    List<CategoryEntity> thirdCategoryList = new ArrayList<>();
    thirdCategoryList.add(new CategoryEntity("221", "电磁炉", false));
    thirdCategoryList.add(new CategoryEntity("222", "电饭煲", false));
    categoryMap.put(new CategoryEntity("121", "厨房用品", false), thirdCategoryList);

    List<CategoryEntity> fourthCategoryList = new ArrayList<>();
    fourthCategoryList.add(new CategoryEntity("231", "Java8函数式编程", false));
    fourthCategoryList.add(new CategoryEntity("232", "美的历程", false));
    fourthCategoryList.add(new CategoryEntity("232", "黑客与画家", false));
    fourthCategoryList.add(new CategoryEntity("232", "代码的未来", false));
    categoryMap.put(new CategoryEntity("131", "自营书籍", false), fourthCategoryList);

    return categoryMap;
}

Gson的序列化和反序列示例代码:

private void gsonTest() {
    LinkedHashMap<CategoryEntity, List<CategoryEntity>> linkedHashMap = createMap();
    String toJson = GsonUtil.getInstance().toJson(linkedHashMap);
    LOG.D(TAG, "toJson = " + toJson);

    LinkedHashMap<CategoryEntity, List<CategoryEntity>> parserMap =
            GsonUtil.getInstance().fromJson(
                    toJson,
                    new TypeToken<LinkedHashMap<CategoryEntity, List<CategoryEntity>>>() {
                    }.getType());
    LOG.D(TAG, "fromJson = " + parserMap.toString());
    List<CategoryEntity> parseCategoryList = parserMap.get(new CategoryEntity("121", "厨房用品", false));
    LOG.D(TAG, "parseCategoryList = " + parseCategoryList.toString());
}

几点说明:

  1. 选用LinkedHashMap(),可以确保按位置顺序反序列化
  2. GsonUtil单例模式,反序列化类型通过TypeToken来指定

序列化结果如下:

[
[
    {
        "id": "101",
        "isSelected": true,
        "name": "全部分类"
    },
    [
        {
            "id": "201",
            "isSelected": true,
            "name": "全部分类"
        }
    ]
],
[
    {
        "id": "111",
        "isSelected": false,
        "name": "儿童玩具"
    },
    [
        {
            "id": "211",
            "isSelected": false,
            "name": "遥控赛车"
        },
        {
            "id": "212",
            "isSelected": false,
            "name": "乐高积木"
        },
        {
            "id": "213",
            "isSelected": false,
            "name": "泰迪小熊"
        }
    ]
],
[
    {
        "id": "121",
        "isSelected": false,
        "name": "厨房用品"
    },
    [
        {
            "id": "221",
            "isSelected": false,
            "name": "电磁炉"
        },
        {
            "id": "222",
            "isSelected": false,
            "name": "电饭煲"
        }
    ]
],
[
    {
        "id": "131",
        "isSelected": false,
        "name": "自营书籍"
    },
    [
        {
            "id": "231",
            "isSelected": false,
            "name": "Java8函数式编程"
        },
        {
            "id": "232",
            "isSelected": false,
            "name": "美的历程"
        },
        {
            "id": "232",
            "isSelected": false,
            "name": "黑客与画家"
        },
        {
            "id": "232",
            "isSelected": false,
            "name": "代码的未来"
        }
    ]
]
] 

小结:
Gson序列化LinkedHashMap, 它会把Map的Key部分和Value部分放在同一层级,作为数组的元素。

Jackson与Map\<Object,Object>

使用jackson序列化/反序列化代码:

private void jackJsonTest(){
    LinkedHashMap<CategoryEntity, List<CategoryEntity>> linkedHashMap = createMap();
    String json = JacksonUtil.getInstance().toJson(linkedHashMap);
    LOG.D(TAG, "jackJson = " + json);

    LinkedHashMap<CategoryEntity, List<CategoryEntity>> parseMap = JacksonUtil.getInstance().fromJson(
            json,
            new com.fasterxml.jackson.core.type.TypeReference<LinkedHashMap<CategoryEntity,List<CategoryEntity>>>() {});
    LOG.D(TAG, "parse map = " + parseMap.toString());
    List<CategoryEntity> categoryEntityList = parseMap.get(new CategoryEntity("121", "厨房用品", false));
    LOG.D(TAG, "jackson list = " + categoryEntityList.toString());
}

说明:

  1. JacksonUtil使用单例,通过TypeReference指定jackson反序列的类型

Jackson解析结果如下:

{
"CategoryEntity{id='101', name='全部分类', isSelected=true}": [
    {
        "id": "201",
        "name": "全部分类",
        "selected": true
    }
],
"CategoryEntity{id='111', name='儿童玩具', isSelected=false}": [
    {
        "id": "211",
        "name": "遥控赛车",
        "selected": false
    },
    {
        "id": "212",
        "name": "乐高积木",
        "selected": false
    },
    {
        "id": "213",
        "name": "泰迪小熊",
        "selected": false
    }
],
"CategoryEntity{id='121', name='厨房用品', isSelected=false}": [
    {
        "id": "221",
        "name": "电磁炉",
        "selected": false
    },
    {
        "id": "222",
        "name": "电饭煲",
        "selected": false
    }
],
"CategoryEntity{id='131', name='自营书籍', isSelected=false}": [
    {
        "id": "231",
        "name": "Java8函数式编程",
        "selected": false
    },
    {
        "id": "232",
        "name": "美的历程",
        "selected": false
    },
    {
        "id": "232",
        "name": "黑客与画家",
        "selected": false
    },
    {
        "id": "232",
        "name": "代码的未来",
        "selected": false
    }
]
}

小结:Jackson在序列化Map时,会把Map的Key对应的对象默认解析成字符串;但是这个字符串格式不是Json的格式,通过添加自定义Map的键的序列化解析类和反序列化解析类,可以实现对CategoryEntity对象Json序列化/反序列化

public JacksonParser() {
    mObjectMapper = new ObjectMapper();
    SimpleModule simpleModule = new SimpleModule();
    simpleModule.addKeyDeserializer(CategoryEntity.class, new MapKeyDeserializer());
    simpleModule.addKeySerializer(CategoryEntity.class, new MapKeySerializer(CategoryEntity.class));
    mObjectMapper.registerModule(simpleModule);
}

Fastjson与Map\<Object,Object>

Fastjson序列化和反序列示例

1
2
3
4
5
private void fastJsonTest() {
LinkedHashMap<CategoryEntity, List<CategoryEntity>> linkedHashMap = createMap();
String s = FastJsonUtil.getInstance().toJson(linkedHashMap);
LOG.D(TAG, "fastJson = " + s);
}

说明:

  1. 单例FastJsonUtil,通过TypeReference来指定反序列化类型

解析结果

{
{"id":"101","name":"全部分类","selected":true}
    :[{"id":"201","name":"全部分类","selected":true}],

{"id":"111","name":"儿童玩具","selected":false}
    :[{"id":"211","name":"遥控赛车","selected":false},{"id":"212","name":"乐高积木","selected":false},{"id":"213","name":"泰迪小熊","selected":false}],

{"id":"121","name":"厨房用品","selected":false}
    :[{"id":"221","name":"电磁炉","selected":false},{"id":"222","name":"电饭煲","selected":false}],

{"id":"131","name":"自营书籍","selected":false}
    :[{"id":"231","name":"Java8函数式编程","selected":false},{"id":"232","name":"美的历程","selected":false},{"id":"232","name":"黑客与画家","selected":false},{"id":"232","name":"代码的未来","selected":false}]
}

小结:fastjson序列化Map,生成的字符串不是json格式,Key部分直接序列化成对象。通过{}:[]这种格式来表现Map\<CategoryEntity,List\>的key和value。

小结

开发中,较少会遇到需要序列化Map\<Object,Object>,即使遇到了也可以通过改变实体类的数据结构来规避这种解析,把Key的Object对象放入到Value的对象里,组成新的实体类,而Map的Key用String来表示。因此,本例的Map\<CategoryEntity,List\>,可以把Map的Key部分的对象Category放入到List集合的里。这样,在某些场景,把二维层面的数据,降为一维数据,通过链式的调用来获取原来二维的数据,把数据维度转化成了链式调用的深度。

示例代码上传到Github上了,欢迎Star,传送门