---
title: "snack4 - JsonPath  扩展定制"
---


### 1、查询上下文的关键属性（与定制相关）

| 属性                | 描述                       | 备注                                        |
| ------------ | --------------- | ------------------------- |
| `isMultiple`        | 是否为多节点输出    | 前面执行过 `..x` 或 `*` 或 `[?]`。通过 `fun()` 聚合后重置为 `false`     |
| `isExpanded`     | 是否已展开              | 前面执行过 `..x` 或 `*`     |
| `isDescendant`   | 是否有后代             | 前面执行过 `..x`     |
| `isFiltered`     | 是否在过滤器中        | 当前正处于 `[?]` 过滤器内部     |
| `forJaywayMode`  | 是否为 Jayway 兼容模式 | 对应特性 `Feature.JsonPath_JaywayMode`     |

查询时，接口输入的是一个节点（内部会变成一个单节点的节点列表），通过执行过 `..x`（展开后代） 或 `*`（展开子代） 或 `[?]`（用子代过滤） 片段后，会变成多节点的节点列表。即 `isMultiple==true`。


使用 `..x` 或 `*` 时，会展开后代或子代。即 `isExpanded==true`。

使用 `..x` 时，会展开后代，即有后代了 `isDescendant==true`。


### 2、操作符定制参考

示例：

```java
public class OperatorDemo {
    public static void main(String[] args) {
        //::定制操作符（已预置）
        OperatorLib.register("startsWith", (ctx, node, term) -> {
            ONode leftNode = term.getLeftNode(ctx, node);

            if (leftNode.isString()) {
                ONode rightNode = term.getRightNode(ctx, node);
                if (rightNode.isNull()) {
                    return false;
                }

                return leftNode.getString().startsWith(rightNode.getString());
            }
            return false;
        });

        //::检验效果
        assert ONode.ofJson("{'list':['a','b','c']}")
                .select("$.list[?@ startsWith 'a']")
                .size() == 1;
    }
}
```

了解接口 Operator 接口和参数：

```java
package org.noear.snack4.jsonpath;

import org.noear.snack4.ONode;
import org.noear.snack4.jsonpath.filter.Term;

@FunctionalInterface
public interface Operator {
    /**
     * 应用
     *
     * @param ctx  查询上下文
     * @param node 目标节点
     * @param term 逻辑表达式项
     */
    boolean apply(QueryContext ctx, ONode node, Term term);
}
```

| 参数     | 描述              | 
| ------ | ---------- | 
| ctx        | 查询上下文    | 
| node     | 当前节点        | 
| term     | 逻辑项描述（左操作元，操作符?，右操作元?）。同时支持一元、二元、三元操作的描述     | 

已预置的操作符（`OperatorLib`）：

| 操作符 | 说明 |
| -------- | -------- |
| `==`（或 `=`） | 相等（兼容 sql 表达式） |
| `!=` | 不相等 |
| `>`、`>=`、`<`、`<=` | 大小比较 |
| `=~` | 正则匹配 |
| `in` / `nin` | 在 / 不在集合中 |
| `subsetof` / `anyof` / `noneof` | 子集 / 任意命中 / 无一命中 |
| `size` / `empty` | 大小判断 / 空判断 |
| `startsWith` / `endsWith` / `contains` | 字符串前缀 / 后缀 / 包含 |

### 3、扩展函数定制参考：

函数（或扩展函数）有两种用途（使用同一个接口开发）：

* 过滤函数，用在过滤器中。
* 聚合函数，用在选择器中。

示例（本示例定制的函数可作： 过滤函数 或 聚合函数 使用）：

```java
package org.noear.snack4.jsonpath;

import org.noear.snack4.ONode;
import org.noear.snack4.jsonpath.FunctionLib;
import org.noear.snack4.jsonpath.JsonPathException;

public class FunctionDemo {
    public static void main(String[] args) {
        //定制 length 函数（已预置）
        FunctionLib.register("length", (ctx, argNodes) -> {
            if (argNodes.size() != 1) {
                throw new JsonPathException("Requires 1 parameters");
            }

            ONode arg0 = argNodes.get(0); //节点列表（选择器的结果）

            if (ctx.isMultiple()) {
                return ctx.newNode(arg0.getArray().size());
            } else {
                if (arg0.getArray().size() > 0) {
                    ONode n1 = arg0.get(0);

                    if (n1.isArray()) return ctx.newNode(n1.getArray().size());
                    if (n1.isObject()) return ctx.newNode(n1.getObject().size());

                    if (ctx.hasFeature(Feature.JsonPath_JaywayMode) == false) {
                        if (n1.isString()) return ctx.newNode(n1.getString().length());
                    }
                }

                return ctx.newNode();
            }
        });

        //检验效果//out: 3
        System.out.println(ONode.ofJson("[1,2,3]")
                .select("$.length()")
                .toJson());
    }
}
```

了解接口 Function 接口和参数：

```java
package org.noear.snack4.jsonpath;

import org.noear.snack4.ONode;
import java.util.List;

@FunctionalInterface
public interface Function {
    /**
     * 应用
     *
     * @param ctx      查询上下文
     * @param argNodes 参数节点列表
     */
    ONode apply(QueryContext ctx, List<ONode> argNodes);
}
```

| 参数                  | 描述              |  
| ------------ | ----------- | 
| ctx                    | 查询上下文    |   
| argNodes          | 参数节点列表     |  

已预置的函数（`FunctionLib`）：

| 函数 | 说明 |
| -------- | -------- |
| `min` / `max` / `avg` / `stddev` | 数值聚合 |
| `sum` | 求和 |
| `size` / `length` / `count` | 长度计数 |
| `keys` | 键集合 |
| `concat` / `append` | 拼接 |
| `first` / `last` | 首尾节点 |
| `index` | 按索引取节点 |
| `match` / `search` | 匹配 / 查找 |
| `value` | 取值 |

### 4、查询上下文 QueryContext

QueryContext 对整个查询定制工作极为重要：hasFeature 可以检查特性？getMode 可以知道当前是什么模式（查询，生成，删除）？等。

```java
public interface QueryContext {
    /**
     * 有使用标准？
     */
    boolean hasFeature(Feature feature);

    /**
     * Jayway 兼容模式
     */
    boolean forJaywayMode();

    /**
     * 在过滤器中的
     */
    boolean isFiltered();

    /**
     * 是否多输出（前面执行过 `..x` 或 `*` 或 `[?]`）
     */
    boolean isMultiple();

    /**
     * 是否已展开（前面执行过 `..x` 或 `*`）
     */
    boolean isExpanded();

    /**
     * 是否有后代选择（前面执行过 `..x`）
     */
    boolean isDescendant();

    /**
     * 查询根节点
     */
    ONode getRoot();

    /**
     * 查询模式
     */
    QueryMode getMode();

    /**
     * 获取根选项配置
     */
    Options getOptions();

    /**
     * 缓存获取
     */
    <T> T cacheIfAbsent(String key, Function<String, ?> mappingFunction);

    /**
     * 内嵌查询（`@.user.name`）
     */
    QueryResult nestedQuery(ONode target, JsonPath query);

    /**
     * 新建节点
     */
    default ONode newNode() {
        return new ONode(getOptions());
    }

    /**
     * 新建节点
     */
    default ONode newNode(Object value) {
        return new ONode(getOptions(), value);
    }
}
```
