美文网首页
Jackson复杂Json的快速解析

Jackson复杂Json的快速解析

作者: 小胖学编程 | 来源:发表于2023-03-26 21: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地址

    相关文章

      网友评论

          本文标题:Jackson复杂Json的快速解析

          本文链接:https://www.haomeiwen.com/subject/sylardtx.html