JsonMediate解决JSON读写逻辑的脏乱差

2019-10-16  本文已影响0人  EagleStrike

后台开发Restful框架最常用的数据类型无疑就是json了,没有固定的数据格式这一特点使得json数据可以在快速迭代的项目中也能很好的担任关键数据传输类型的角色,而且搭配fastjson等快捷工具包能够更加方便的使用。

然而……对于强迫癌晚期的我来说总感觉少了点什么:

灵活多变的数据形式带给json平易近人的优点的同时,也最大限度的让你身边肆无忌惮的队友们封装成了各种乱七八糟的结构,层级一多,代码中就会出现一片一片的手风琴;
而且在数据调用过程中,往往需要反复的一层层拆解json数据以获取你需要的那一两个字段;
也有的时候,一坨json数据摆在那里,你只想往里面第n层的第m条数据对象附一个属性值……拆吧。

前言就先到这里,直接上代码,我把文档内容也附在代码中写的比较详尽了:


package com.strike.json;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;

import java.util.Map;

/**
 * @ClassName JsonMediate
 * @Description TODO JSON数据解析工具类
 * @Author EagleStrike
 * @Created 2019/10/14
 */
public class JsonMediate {

    /**
     * 工具类概要
     *
     *  数据转换顺序:
     *      1) Map → info → JSON
     *      2) JSON → Map → info
     *      3) String → JSON → Map → info
     *
     *  数据类型:
     *      Map     : 长键:数据 键值对 键名以"."/"[]"层级拼接展示对象/集合包含关系 ( contentMap )
     *      info    : 属性值 + 子节点 记录当前节点信息 可由Map转换 属性值以键值对存储 节点值类型同为JsonMediate级联 数组情况下键名为[n]方便检索 ( properties / children )
     *      JSON    : JSON数据实体 可由info生成
     *      String  : 仅构建时可使用 经fastjson直接转换生成JSON数据闭环
     *
     *  构造方式:
     *      static getInstance(payload)     payload:可通过Map、JSON、JSONString构建 构建类型可为JSONObject/JSONArray
     *      构造逻辑默认完成info信息渲染
     *
     *  数据渲染:
     *      flushContentMap : 更新Map跨级属性键值对缓存 ( * 需要具备JSON数据 即content字段已赋值/渲染 )
     *      flushContent    : 更新JSON数据实体 ( * 需要具备info层级数据信息 即properties、children已渲染 )
     *      renderInfo      : 更新info层级数据信息 ( * 需要具备Map跨技术性键值对缓存 )
     *
     *  数据操作:
     *      put     : 通过Map跨级属性键值对跨级赋值
     *      putAll  : 同put 批量赋值
     *
     *      getInteger  : 单值类数据获取 通过Map跨级属性键值对跨级获取Integer值
     *      getDouble   : 同上 获取Double值
     *      getFloat    : 同上 获取Float值
     *      getString   : 同上 获取String值
     *      getObject   : 同上 获取Object值 用于常用单一数据格式无法覆盖类型问题
     *
     *      getJSONObject   : 通过info层级数据信息获取制定层级节点数据对象
     *      getJSONArray    : 通过info层级数据信息获取制定层级节点数据数组
     */

    // 数据类型 实体
    private static final int TYPE_OBJECT = 0;
    // 数据类型 数组
    private static final int TYPE_ARRAY  = 1;

    /** ——————————————————————————————  构造逻辑  —————————————————————————————— */

    /**
     * @Title getInstanceByMap
     * @Description TODO 构造逻辑 跨级键值对构造
     *
     * @Param: contentMap
     * @Return: com.strike.json.JsonMediate
     * @Author EagleStrike
     * @Created 2019/10/16 下午2:17
     */
    public static JsonMediate getInstanceByMap(Map<String, Object> contentMap) {
        JsonMediate mediate = new JsonMediate();
        mediate.setContentMap(contentMap);
        mediate
                .renderInfo();
        return mediate;
    }

    /**
     * @Title getInstance
     * @Description TODO 构造逻辑 JSON构造
     *
     * @Param: content
     * @Return: com.strike.json.JsonMediate
     * @Author EagleStrike
     * @Created 2019/10/16 下午2:18
     */
    public static <T extends JSON> JsonMediate getInstance(T content) {
        JsonMediate mediate = new JsonMediate();
        mediate.setContent(content);
        mediate
                .flushContentMap()
                .renderInfo();
        return mediate;
    }

    /**
     * @Title getInstance
     * @Description TODO 构造逻辑 String构造
     *
     * @Param: contentString
     * @Return: com.strike.json.JsonMediate
     * @Author EagleStrike
     * @Created 2019/10/16 下午2:18
     */
    public static JsonMediate getInstance(String contentString) {
        JSON content = null;
        try {
            content = JSONObject.parseObject(contentString);
        } catch (Exception ignored) {}
        try {
            content = JSONArray.parseArray(contentString);
        } catch (Exception ignored) {}
        if (null == content) {
            throw new IllegalArgumentException("Content String cannot parse to JSONObject or JSONArray.");
        }
        return JsonMediate.getInstance(content);
    }

    private JsonMediate() {}

    /** ——————————————————————————————  开放逻辑  —————————————————————————————— */

    /**
     * @Title flushContentMap
     * @Description TODO MAP数据渲染 content → contentMap
     *
     * @Return: com.strike.json.JsonMediate
     * @Author EagleStrike
     * @Created 2019/10/16 下午3:09
     */
    public JsonMediate flushContentMap() {
        this.contentMap = flushInnerContentMap(null, this.content);
        return this;
    }

    /**
     * @Title renderInfo
     * @Description TODO 层级数据渲染 contentMap → properties + children
     *
     * @Return: com.strike.json.JsonMediate
     * @Author EagleStrike
     * @Created 2019/10/16 下午3:03
     */
    public JsonMediate renderInfo() {
        Map<String, Map<String, Object>> childContentMaps = Maps.newHashMap();
        for (Map.Entry<String, Object> contentEntry : this.contentMap.entrySet()) {
            String[] distributeInfo = distributeHelper(contentEntry.getKey());
            String childName = distributeInfo[0];
            String childProperty = distributeInfo[1];
            int type = TYPE_OBJECT;
            if (childName.startsWith("[")) {
                type = TYPE_ARRAY;
            }
            this.dataTypeInitHelper(type);
            if (null == childProperty) {
                this.properties.put(childName, contentEntry.getValue());
                continue;
            }
            Map<String, Object> childContentMap = childContentMaps.get(childName);
            childContentMap = null == childContentMap ? Maps.newHashMap() : childContentMap;
            childContentMap.put(childProperty, contentEntry.getValue());
            childContentMaps.put(childName, childContentMap);
        }
        for (Map.Entry<String, Map<String, Object>> childContentEntry : childContentMaps.entrySet()) {
            this.children.put(childContentEntry.getKey(), JsonMediate.getInstanceByMap(childContentEntry.getValue()));
        }
        return this;
    }

    /**
     * @Title flushContent
     * @Description TODO JSON数据渲染 properties + children → content
     *
     * @Return: com.strike.json.JsonMediate
     * @Author EagleStrike
     * @Created 2019/10/16 下午2:39
     */
    public JsonMediate flushContent() {
        JSON body;
        if (TYPE_OBJECT == this.type) {
            body = new JSONObject();
            ((JSONObject) body).putAll(properties);
            for (Map.Entry<String, JsonMediate> childEntry : children.entrySet()) {
                ((JSONObject) body).put(
                        childEntry.getKey(),
                        childEntry.getValue()
                                .renderInfo()
                                .flushContent()
                                .getContent()
                );
            }
        } else if (TYPE_ARRAY == this.type) {
            body = new JSONArray();
            while (((JSONArray) body).size() < properties.size() + children.size()) {
                String rankIdentity = String.format("[%s]", ((JSONArray) body).size());
                for (Map.Entry<String, Object> propertyEntry : properties.entrySet()) {
                    if (!rankIdentity.equals(propertyEntry.getKey())) {
                        continue;
                    }
                    ((JSONArray) body).add(propertyEntry.getValue());
                    break;
                }
                for (Map.Entry<String, JsonMediate> childEntry : children.entrySet()) {
                    if (!rankIdentity.equals(childEntry.getKey())) {
                        continue;
                    }
                    ((JSONArray) body).add(
                            childEntry.getValue()
                                    .renderInfo()
                                    .flushContent()
                                    .getContent()
                    );
                    break;
                }
            }
        } else {
            throw new IllegalArgumentException("Content type undefined.");
        }
        this.content = body;
        return this;
    }

    /**
     * @Title put
     * @Description TODO 添加值 ( 获取最新JSON数据需先执行flushContent )
     *
     * @Param: key
     * @Param: value
     * @Return: com.strike.json.JsonMediate
     * @Author EagleStrike
     * @Created 2019/10/16 下午3:59
     */
    public JsonMediate put(String key, Object value) {
        this.contentMap.put(key, value);
        return this;
    }

    /**
     * @Title putAll
     * @Description TODO 批量添加值 ( 获取最新JSON数据需先执行flushContent )
     *
     * @Param: contentMap
     * @Return: com.strike.json.JsonMediate
     * @Author EagleStrike
     * @Created 2019/10/16 下午4:16
     */
    public JsonMediate putAll(Map<String, Object> contentMap) {
        this.contentMap.putAll(contentMap);
        this.renderInfo();
        return this;
    }

    public Integer getInteger(String key) {
        return getValue(key);
    }

    public Double getDouble(String key) {
        return getValue(key);
    }

    public Float getFloat(String key) {
        return getValue(key);
    }

    public String getString(String key) {
        return getValue(key);
    }

    public Object getObject(String key) {
        return getValue(key);
    }

    public JSONObject getJSONObject(String key) {
        String[] distributeInfo = distributeHelper(key);
        String childName = distributeInfo[0];
        String childProperty = distributeInfo[1];
        if (null == childProperty) {
            return this.children
                    .get(childName)
                    .flushContent()
                    .getContent();
        } else {
            return this.children.get(childName).getJSONObject(childProperty);
        }
    }

    public JSONArray getJSONArray(String key) {
        String[] distributeInfo = distributeHelper(key);
        String childName = distributeInfo[0];
        String childProperty = distributeInfo[1];
        if (null == childProperty) {
            return this.children
                    .get(childName)
                    .flushContent()
                    .getContent();
        } else {
            return this.children.get(childName).getJSONArray(childProperty);
        }
    }

    /** ——————————————————————————————  封装逻辑  —————————————————————————————— */

    /**
     * @Title flushInnerContentMap
     * @Description TODO 递归MAP数据渲染 content → contentMap
     *
     * @Param: key
     * @Param: model
     * @Return: java.util.Map<java.lang.String,java.lang.Object>
     * @Author EagleStrike
     * @Created 2019/10/16 下午3:38
     */
    private <T extends JSON> Map<String, Object> flushInnerContentMap(String key, T model) {
        Map<String, Object> contentMap = Maps.newHashMap();
        StringBuilder prefix = new StringBuilder()
                .append(StringUtils.isBlank(key) ? "" : key)
                .append(null == key || model instanceof JSONArray ? "" : ".");
        if (model instanceof JSONObject) {
            String identity = "%s%s";
            for (Map.Entry<String, Object> contentEntry : ((JSONObject) model).entrySet()) {
                if (contentEntry.getValue() instanceof JSON) {
                    contentMap.putAll(flushInnerContentMap(String.format(identity, prefix, contentEntry.getKey()), (T) contentEntry.getValue()));
                } else {
                    contentMap.put(String.format(identity, prefix, contentEntry.getKey()), contentEntry.getValue());
                }
            }
        } else if (model instanceof JSONArray) {
            String identity = "%s[%s]";
            for (int i = 0; i < ((JSONArray) model).size(); i++) {
                Object item = ((JSONArray) model).get(i);
                if (item instanceof JSON) {
                    contentMap.putAll(flushInnerContentMap(String.format(identity, prefix, i), (T) item));
                } else {
                    contentMap.put(String.format(identity, prefix, i), item);
                }
            }
        }
        return contentMap;
    }

    /**
     * @Title getValue
     * @Description TODO 获取属性值
     *
     * @Param: key
     * @Return: T
     * @Author EagleStrike
     * @Created 2019/10/16 下午5:35
     */
    private <T> T getValue(String key) {
        return (T) contentMap.get(key);
    }

    /** ——————————————————————————————  辅助逻辑  —————————————————————————————— */

    /**
     * @Title distributeHelper
     * @Description TODO 子集分发辅助逻辑 key → 子节点名称 + 子节点key
     *
     * @Param: key
     * @Return: java.lang.String[]
     * @Author EagleStrike
     * @Created 2019/10/16 下午2:18
     */
    private String[] distributeHelper(String key) {
        String childName;
        String childProperty;
        if (!key.contains(".") && (!key.contains("[") || key.indexOf("[") == 0)) {
            return new String[] { key, null };
        }
        key = key.replaceAll("\\[", ".[");
        key = StringUtils.removeStartIgnoreCase(key, ".");
        childName = key.substring(0, key.indexOf("."));
        childProperty = key.substring((childName.length() + 1));
        childProperty = childProperty.replaceAll("\\.\\[", "[");
        return new String[] { childName, childProperty };
    }

    /**
     * @Title dataTypeInitHelper
     * @Description TODO 数据格式管理辅助逻辑
     *
     * @Param: type
     * @Author EagleStrike
     * @Created 2019/10/16 下午3:00
     */
    private void dataTypeInitHelper(int type) {
        if (null == this.type || this.type == type) {
            this.type = type;
            return;
        }
        throw new IllegalArgumentException("Make sure the property have a certain data type.");
    }

    /** ——————————————————————————————  属性字段  —————————————————————————————— */

    /**
     * 数据类型 0-对象 1-集合
     */
    private Integer type = null;
    /**
     * 跨级属性键值对缓存
     */
    private Map<String, Object> contentMap = Maps.newHashMap();
    /**
     * 属性集合
     */
    private Map<String, Object> properties = Maps.newHashMap();
    /**
     * 子节点集合
     */
    private Map<String, JsonMediate> children = Maps.newHashMap();
    /**
     * JSON数据实体
     */
    private JSON content;

    public int getType() {
        return type;
    }

    public Map<String, Object> getContentMap() {
        return contentMap;
    }

    public Map<String, Object> getProperties() {
        return properties;
    }

    public Map<String, JsonMediate> getChildren() {
        return children;
    }

    public <T extends JSON> T getContent() {
        return (T) content;
    }

    protected JsonMediate setType(int type) {
        this.type = type;
        return this;
    }

    protected JsonMediate setContentMap(Map<String, Object> contentMap) {
        this.contentMap = contentMap;
        return this;
    }

    protected JsonMediate setProperties(Map<String, Object> properties) {
        this.properties = properties;
        return this;
    }

    protected JsonMediate setChildren(Map<String, JsonMediate> children) {
        this.children = children;
        return this;
    }

    protected JsonMediate setContent(JSON content) {
        this.content = content;
        return this;
    }
}

附上一段测试代码


package com.strike.json;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.junit.Test;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
 * @ClassName TestJsonMediate
 * @Description TODO JsonMediate工具调用测试
 * @Author EagleStrike
 * @Created 2019/10/16
 */
public class TestJsonMediate {

    private static JSONObject data;
    private static JSONArray array;
    static {
        data = new JSONObject();
        array = new JSONArray();

        JSONObject dataCopy = new JSONObject();
        JSONArray arrayCopy = new JSONArray();

        // 初始化测试用数据对象
        data.put("current", System.currentTimeMillis());
        data.put("message", "字符串数据测试");
        data.put("info", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"));

        dataCopy.putAll(data);

        JSONObject innerObject = new JSONObject();
        innerObject.put("test", "测试多级内容");
        innerObject.put("date", new Date());
        innerObject.put("blank", "");

        array.add(new Date());
        array.add("数组添加字符串测试");
        array.add(1234567);
        array.add(125.567);
        array.add(new IllegalArgumentException("随便装了个异常信息"));
        array.add(innerObject);

        arrayCopy.addAll(array);

        array.add(dataCopy);

        data.put("array", arrayCopy);
    }

    @Test
    public void test() {

        /**
         * JSONObject数据构建
         */
        JsonMediate dataMediate  = JsonMediate.getInstance(data);
        // 构建默认执行renderInfo() 打印properties与children信息
        Map<String, Object> properties = dataMediate.getProperties();
        for (Map.Entry<String, Object> property : properties.entrySet()) {
            System.err.println(property.getKey() + "::" + property.getValue());
        }
        Map<String, JsonMediate> children = dataMediate.getChildren();
        for (Map.Entry<String, JsonMediate> child : children.entrySet()) {
            System.err.println(child.getKey() + "::" + child.getValue().flushContent().getContent());
        }

        System.out.println(dataMediate.getInteger("array[2]"));
        System.out.println(dataMediate.getObject("array[5].date"));

        JsonMediate arrayMediate = JsonMediate.getInstance(array);
        arrayMediate.put("[5].blank", "测试字符串跨级写入");

        System.err.println(arrayMediate.renderInfo().flushContent().getContent());
    }
}

为了方便引用,我直接写了一个工具类,对外依赖功能比较少。
JsonMediate实体类使用Map<String, Object>缓存长键键值对数据,可以直接通过长键获取最内层属性值,也可以通过getJSONObject和getJSONArray获取指定层级上的对象/集合数据,数据转换先后顺序在代码中的文档部分也说得比较明白了。Map长键键值对实现了最简化的缓存性质,可以在调用方法中传递JsonMediate多次调用,缓存管理交给gc我还是比较放心的,毕竟深层的东西写出来可读性太差了。

我的一点微不足道的积累,开发的道路上,希望能够跟更多的人互通有无。

上一篇下一篇

猜你喜欢

热点阅读