Jackson复杂Json的快速解析

2023-03-26  本文已影响0人  小胖学编程

背景

在面对复杂Json字符串,但只解析部分字段场景时。

如何优化jackson反序列化字符串的过程来减少不必要的耗时操作(对象重复创建、无用路径解析),实现一种性能更高,资源消耗更少的json解析能力?

使用方式

JsonRowConverter类的构造参数为要解析json字符串中各个字段的路径,嵌套路径以 . 分割,对json字符串各个路径的解析结果会转换为string字符串,并按照构造参数的路径顺序返回String数组,示例代码如下。

// 用法JsonRowConverter构造方法为变长参数,传入要解析的多个路径
// 对下面例子,路径a返回:"1", 路径b.c返回 "xx", 路径b返回 {"c":"xx","d":[1,2,3],"e":[[1,2,3]]}
// 路径b.d返回[1,2,3],路径b.d.1返回2,路径b.e.0.0返回1
String json = "{\"a\":1,\"b\":{\"c\":\"xx\",\"d\":[1,2,3],\"e\":[[1,2,3]]}}";
JsonRowConverter converter = new JsonRowConverter("a","b","b.c", 'b.d.1');
String[] result = converter.process(json);

原理

jackson解析思路

jackson是一种开源主流的json解析工具,详情可以参考:https://github.com/FasterXML/jackson

jackson常见有两种解析场景,一种为将json解析为JsonNode tree,另一种将json字符串解析为java类。

ObjectMapper mapper = new ObjectMapper();
// 1. 解析成jsonnode tree
JsonNode node = mapper.readTree(json);
// 2. 解析成java对象
HashMap<String, String> map = (HashMap<String, String>) mapper.readValue(json, Map.class);

两种解析json的方法逻辑相识,可以分为下面几个部分:

  1. 对json字符串进行词法解析,解析成JsonToken组合;
  2. 将调用DefaultSerializationContext的readRootValue方法。
  3. 将调用DefaultSerializationContext的readRootValue方法。

jackson定义了一系列json反序列化类,不同的反序列化类会将json反序列化为不同的类型。比如MapDeserializer类会将jsontoken集合解析为Map类型,而JsonNode deserializer类会将jsontoken集合解析为JsonNode类型。

但是jackson提供的官方解析方法为了保证扩展性,存在会将大量的无用字段递归解析,并且会在json每个路径节点创建不同的对象。

比如:对于json字符串:"{"a":1,"b":{"c":"xx","d":[1,2,3],"e":[[1,2,3]]}}"

哪怕我们只想解析"a"这个字段的值,当调用jackson的官方解析方法时候(比如readTree),也会将b、b.c、b.d等等字段全部解析出来,并且每个节点构造jsonnode的对象。

新的设计思路

针对jackson官方解析方案存在的两点问题,可以给出相应的解决方案:
a. 无效字段解析:通过解析路径配置,解析节点树,遇到不需要解析的字段时快速跳过;
b. 对象重复创建:将对象存储到节点树上,复用对象,不需要重复创建对象。

  1. 构建节点树


    构建节点树.png
  2. 词法解析json字符串,生成JsonToken集合。

  3. 深度遍历JsonToken,赋值节点树,返回结果

代码实现

引入依赖

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.0</version>
        </dependency>

定义pojo

public abstract class AbstractNode<K, V, C extends AbstractNode> {
    private long version = 0;
    private boolean isLeaf = false;
    private K name;
    private V value = null;
    private C parent = null;
    private HashMap<K, C> children;

    public AbstractNode(K name) {
        this.name = name;
    }
    public abstract void _init();
    public boolean isLeaf() {
        return isLeaf;
    }

    public void setLeaf(boolean leaf) {
        isLeaf = leaf;
    }

    public HashMap<K, C> getChildren() {
        return children;
    }

    public void setChildren(HashMap<K, C> children) {
        this.children = children;
    }

    public long getVersion() {
        return version;
    }

    public void setVersion(long version) {
        this.version = version;
    }

    public K getName() {
        return name;
    }

    public void setName(K name) {
        this.name = name;
    }

    public V getValue() {
        return value;
    }

    public void setValue(V value) {
        this.value = value;
    }

    public C getParent() {
        return parent;
    }

    public void setParent(C parent) {
        this.parent = parent;
    }
}
public class JsonNode extends AbstractNode<String, String, JsonNode> {
    private int start = -1;
    private int end = -1;
    public JsonNode(String name) {
        super(name);
    }

    public void _init() {
        setChildren(new HashMap<String, JsonNode>());
    }

    public int getStart() {
        return start;
    }

    public void setStart(int start) {
        this.start = start;
    }

    public int getEnd() {
        return end;
    }

    public void setEnd(int end) {
        this.end = end;
    }
}

定义解析器


import java.io.IOException;

import org.protojson.pojo.JsonNode;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;


public class JsonRowConverter {
    private static final ObjectMapper MAPPER = new ObjectMapper();
    private String _json;
    private JsonNode root = new JsonNode("root");
    private JsonNode _ptr = root;
    private JsonNode[] row;
    private String[] result;
    private static String[] EMPTY_RESULT;
    private final int NUM;
    private int currentVersion = 0;

    public JsonRowConverter(String... args) {
        NUM = args.length;
        row = new JsonNode[NUM];
        result = new String[NUM];
        EMPTY_RESULT = new String[NUM];
        for (int i = 0; i < args.length; i++) {
            EMPTY_RESULT[i] = null;
        }
        for (int i = 0; i < NUM; i++) {
            JsonNode cur = root;
            String[] paths = args[i].split("\\.");
            for (int j = 0; j < paths.length; j++) {
                if (cur.getChildren() == null) {
                    cur._init();
                }
                if (!cur.getChildren().containsKey(paths[j])) {
                    JsonNode child = new JsonNode(paths[j]);
                    cur.getChildren().put(paths[j], child);
                    child.setParent(cur);
                }
                cur = cur.getChildren().get(paths[j]);
            }
            cur.setLeaf(true);
            row[i] = cur;
        }
    }
    public String[] process(String json) throws IOException {
        currentVersion++;
        _ptr = root;
        _json = json;
        if (json == null || json.length() == 0) {
            return EMPTY_RESULT;
        }
        JsonParser parser = MAPPER.createParser(json);
        parser.nextToken();
        _recursion(_ptr, parser, null);
        return getResult(_json);
    }
    private void _recursion(JsonNode ptr, JsonParser parser, JsonToken skipArray) {
        try {
            while (parser.currentToken() != null) {
                //                System.out.println("parser name:" + parser.currentName());
                //                System.out.println("parser token:" + parser.getCurrentToken().name());
                //                System.out.println("node name:" + ptr.getName());
                switch (parser.currentToken()) {
                    case START_OBJECT:
                        ptr.setVersion(currentVersion);
                        ptr.setStart(parser.currentLocation().getColumnNr() - 2);
                        parser.nextToken();
                        break;
                    case END_OBJECT:
                        ptr.setEnd(parser.getCurrentLocation().getColumnNr() - 1);
                        ptr = ptr.getParent();
                        parser.nextToken();
                        if (skipArray == JsonToken.END_OBJECT) return;
                        break;
                    case START_ARRAY:
                        ptr.setVersion(currentVersion);
                        ptr.setStart(parser.getCurrentLocation().getColumnNr() - 2);
                        int i = 0;
                        while (parser.currentToken() != JsonToken.END_ARRAY) {
                            parser.nextToken();
                            if (ptr.getChildren().containsKey("" + i)) {
                                ptr = ptr.getChildren().get("" + i);
                                if (parser.currentToken() == JsonToken.START_OBJECT) {
                                    _recursion(ptr, parser, JsonToken.END_OBJECT);
                                } else if (parser.currentToken() == JsonToken.START_ARRAY) {
                                    _recursion(ptr, parser, JsonToken.END_ARRAY);
                                } else {
                                    _recursion(ptr, parser, JsonToken.NOT_AVAILABLE);
                                }
                                ptr = ptr.getParent();
                            } else {
                                skip(parser);
                            }
                            ++i;
                        }
                        break;
                    case END_ARRAY:
                        ptr.setEnd(parser.getCurrentLocation().getColumnNr() - 1);
                        ptr = ptr.getParent();
                        parser.nextToken();
                        if (skipArray == JsonToken.END_ARRAY) return;
                        break;
                    case FIELD_NAME:
                        if (ptr.getChildren() != null && ptr.getChildren().containsKey(parser.getCurrentName())) {
                            ptr = ptr.getChildren().get(parser.getCurrentName());
                            ptr.setVersion(currentVersion);
                            if (ptr.getChildren() == null) {
                                parser.nextToken();
                                ptr.setValue(skip(parser,true));
                                ptr = ptr.getParent();
                            }
                        } else {
                            parser.nextToken();
                            skip(parser);
                        }
                        parser.nextToken();
                        break;
                    case VALUE_STRING:
                    case VALUE_NUMBER_FLOAT:
                    case VALUE_NULL:
                    case VALUE_NUMBER_INT:
                    case VALUE_TRUE:
                    case VALUE_FALSE:
                        ptr.setVersion(currentVersion);
                        ptr.setValue(parser.getValueAsString());
                        ptr = ptr.getParent();
                        if (skipArray == JsonToken.NOT_AVAILABLE) return;
                        break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private String[] getResult(String json) {
        for (int i = 0; i < NUM; i++) {
            if (row[i].getVersion() < currentVersion) {
                result[i] = null;
                continue;
            }
            if (row[i].getChildren() != null) {
                result[i] = json.substring(row[i].getStart(), row[i].getEnd());
            } else {
                result[i] = row[i].getValue();
            }
        }
        return result;
    }
    private String skip(JsonParser parser, boolean flag) throws IOException {
        _ptr.setVersion(currentVersion);
        int i = 0, start;
        start = flag ? parser.getCurrentLocation().getColumnNr() - 2 : 0;
        switch (parser.currentToken()) {
            case START_OBJECT:
                i++;
                while (i > 0 && parser.nextToken() != null) {
                    if (parser.currentToken() == JsonToken.START_OBJECT) i++;
                    else if (parser.currentToken() == JsonToken.END_OBJECT) i--;
                }
                return flag ? _json.substring(start, parser.getCurrentLocation().getColumnNr() - 1): null;
            case START_ARRAY:
                i++;
                while (i > 0 && parser.nextToken() != null) {
                    if (parser.currentToken() == JsonToken.START_ARRAY) i++;
                    else if (parser.currentToken() == JsonToken.END_ARRAY) i--;
                }
                return flag ? _json.substring(start, parser.getCurrentLocation().getColumnNr() - 1): null;
            default:
                return flag ? parser.getValueAsString() : null;
        }
    }
    private void skip(JsonParser parser) throws IOException {
        skip(parser, false);
    }
}

附录

github地址

上一篇下一篇

猜你喜欢

热点阅读