FastJsonを用いたJSONツリーのCRUD操作の実現方法

前提準備:JSON文字列

[{
    "id": 1,
    "code": "FLOW_NODE_1",
    "name": "環節A",
    "children": [{
        "id": 2,
        "code": "RULE_NODE_1",
        "name": "規則A"
    }, {
        "id": 3,
        "code": "RULE_NODE_2",
        "name": "規則B"
    }, {
        "id": 4,
        "code": "PARALLEL_NODE_2",
        "name": "並行ノード",
        "children": [{
            "id": 5,
            "code": "RULE_NODE_3",
            "name": "規則C1"
        }, {
            "id": 6,
            "code": "RULE_COLLECTION_1",
            "name": "規則集1",
            "children": [{
                "id": 7,
                "code": "RULE_NODE_4",
                "name": "規則C21"
            }, {
                "id": 8,
                "code": "RULE_NODE_5",
                "name": "規則C22"
            }]
        }]
    }, {
        "id": 9,
        "code": "MUTUAL_NODE_1",
        "name": "互斥ノード",
        "children": [{
            "id": 10,
            "code": "RULE_NODE_6",
            "name": "規則D1"
        }, {
            "id": 11,
            "code": "RULE_NODE_7",
            "name": "規則D2"
        }]
    }]
}, {
    "id": 12,
    "code": "FLOW_NODE_2",
    "name": "環節B"
}]

本記事ではSpring Boot環境下でのFastJson APIの使用例を紹介します。

ステップ1:依存関係の追加

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>${fastjson.version}</version>
</dependency>
  1. 指定したtreeIdのJSONツリーを検索し、前端に返す方法

Controller:

/**
 * @param treeId
 * @return
 */
@ApiOperation(value = "ツリー情報を検索", notes = "ツリー情報を検索")
@RequestMapping(value = "/api/v1/orders/findTree", method = RequestMethod.POST)
public Map findTree(@ApiParam(required = true, value = "ツリーID") @RequestParam Integer treeId) {
    return ActionHelper.responseOk(categoryTreeService.getJsonTree(treeId));
}

Service:

// IDを用いてJSONツリーを検索
public JSONArray getJsonTree(Integer treeId) {
    JSONArray jsonTree = JSONArray.parseArray(categoryTreeRepository.selectJsonTree(treeId));
    return jsonTree;
}
  1. 指定したtreeIdのJSONツリー内にある特定のkey-valueペアを持つノードを検索する方法

Controller:

/**
 * @param treeId  ツリーID
 * @param key     検索するノードのキー
 * @param value    検索するノードの値
 * @return
 */
@ApiOperation(value = "ノード情報を検索", notes = "ノード情報を検索")
@RequestMapping(value = "/api/v1/orders/findNode", method = RequestMethod.POST)
public Map findNode(@ApiParam(required = true, value = "ツリーID") @RequestParam Integer treeId,
                   @ApiParam(required = true, value = "ノードキー") @RequestParam String key,
                   @ApiParam(required = true, value = "ノード値") @RequestParam String value) {
    return ActionHelper.responseOk(categoryTreeService.searchNode(treeId, key, value));
}

Service:

// 特定のkey-valueペアを持つノードを検索
public JSONObject searchNode(Integer treeId, String key, String value) {
    JSONArray jsonTree = JSONArray.parseArray(categoryTreeRepository.selectJsonTree(treeId));
    JSONObject node = search(jsonTree, key, value, new JSONObject());
    return node;
}

/**
 * 特定条件に一致するノードを検索
 * @param body    検索対象のJSON配列
 * @param key     検索条件のキー
 * @param value    検索条件の値
 * @param result  検索結果を格納するJSONObject
 * @return
 */
public static JSONObject search(JSONArray body, String key, Object value, JSONObject result) {
    for (int i = 0; i < body.size(); i++) {
        JSONObject jsonObject = body.getJSONObject(i);
        if (jsonObject.get(key).toString().equals(value.toString())) {
            for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
                result.put(entry.getKey(), entry.getValue());
            }
        } else if (jsonObject.getJSONArray("children") != null) {
            search(jsonObject.getJSONArray("children"), key, value, result);
        }
    }
    return result;
}
  1. 指定したtreeIdのノードを削除する方法

Controller:

@ApiOperation(value = "ノード情報を削除", notes = "ノード情報を削除")
@RequestMapping(value = "/api/v1/orders/delNode", method = RequestMethod.POST)
public Map delNode(@ApiParam(required = true, value = "ツリーID") @RequestParam Integer treeId,
                  @ApiParam(required = true, value = "ノードキー") @RequestParam String key,
                  @ApiParam(required = true, value = "ノード値") @RequestParam String value) {
    return ActionHelper.responseOk(categoryTreeService.deleteNode(treeId, key, value));
}

Service:

// JSONツリーから指定ノードを削除し、子ノードも削除
public JSONObject deleteNode(Integer treeId, String key, String value) {
    JSONArray jsonTree = JSONArray.parseArray(categoryTreeRepository.selectJsonTree(treeId));
    JSONObject node = search(jsonTree, key, value, new JSONObject());
    delete(jsonTree, key, value);
    String updatedTree = jsonTree.toString();
    categoryTreeRepository.updateTree(updatedTree, treeId);
    return node;
}

/**
 * 特定条件に一致するノードを削除
 * @param body    削除対象のJSON配列
 * @param key     削除条件のキー
 * @param value    削除条件の値
 */
public void delete(JSONArray body, String key, Object value) {
    for (int i = 0; i < body.size(); i++) {
        JSONObject jsonObject = body.getJSONObject(i);
        if (jsonObject.get(key).toString().equals(value.toString())) {
            body.remove(i);
            break;
        } else if (jsonObject.getJSONArray("children") != null) {
            delete(jsonObject.getJSONArray("children"), key, value);
        }
    }
}

Mapper:

public interface CategoryTreeRepository extends JpaRepository<CategoryTree, String>, JpaSpecificationExecutor<CategoryTree> {
    // 指定IDのJSONツリー文字列を取得
    @Query(value = "SELECT jsonTree FROM catagory_tree WHERE id = ?1", nativeQuery = true)
    String selectJsonTree(Integer id);

    @Modifying
    @Transactional
    @Query(value = "UPDATE catagory_tree SET jsonTree = ?1 WHERE id = ?2", nativeQuery = true)
    void updateTree(String jsonTreeStr, Integer id);
}
  1. 指定したkey-valueペアノードの下に新規ノードを追加する方法

Controller:

@ApiOperation(value = "新規ノードを作成", notes = "新規ノードを作成")
@RequestMapping(value = "/tree_data/node/{treeId}", method = RequestMethod.POST)
public Map addNode(@ApiParam(required = true, value = "カテゴリツリーID") @PathVariable Integer treeId,
                  @ApiParam(required = true, value = "ノードキー") @RequestParam String key,
                  @ApiParam(required = true, value = "ノード値") @RequestParam String value,
                  @ApiParam(required = true, value = "挿入位置") @RequestParam Integer index,
                  @ApiParam(required = true, value = "ノード情報") @RequestBody Map map) {
    return ActionHelper.responseOk(categoryTreeService.addNode(treeId, key, value, index, map));
}

Service:

// JSONツリーにノードを追加
public JSONArray addNode(Integer treeId, String key, Object value, Integer sequ, Map newNodeMap) {
    JSONArray jsonTree = JSONArray.parseArray(categoryTreeRepository.selectJsonTree(treeId));
    Integer nodeId = (Integer) newNodeMap.get("id");
    JSONObject node = search(jsonTree, "id", nodeId, new JSONObject());

    if (!node.isEmpty()) {
        throw new BadRequestException("指定されたIDのノードが既に存在します");
    }

    JSONObject newNode = new JSONObject(newNodeMap);
    insertNode(jsonTree, key, value, sequ, newNode);
    return jsonTree;
}

/**
 * 指定条件に一致するノードの子要素として新規ノードを挿入
 * @param jsonArray  更新対象のJSON配列
 * @param key       挿入条件のキー
 * @param value     挿入条件の値
 * @param sequ      挿入位置
 * @param node      挿入する新規ノード
 */
public void insertNode(JSONArray jsonArray, String key, Object value, Integer sequ, JSONObject node) {
    for (Object obj : jsonArray) {
        JSONObject jsonObject = (JSONObject) obj;
        JSONArray children = jsonObject.getJSONArray("children");
        if (children != null) {
            if (value.toString().equals(jsonObject.get(key).toString())) {
                if (sequ > children.size()) {
                    children.add(children.size(), node);
                } else {
                    children.add(sequ - 1, node);
                }
                return;
            }
            insertNode(children, key, value, sequ, node);
        } else {
            if (value.toString().equals(jsonObject.get(key).toString())) {
                JSONArray array = new JSONArray();
                array.add(node);
                jsonObject.put("children", array);
                return;
            }
        }
    }
}
  1. 指定したノードIDのノード属性を更新する方法

Controller:

@ApiOperation(value = "ノード情報を更新", notes = "ノード情報を更新")
@RequestMapping(value = "/tree_data/node/{treeId}/{id}", method = RequestMethod.PATCH)
public Map updateNode(@ApiParam(required = true, value = "カテゴリツリーID") @PathVariable Integer treeId,
                    @ApiParam(required = true, value = "ノードID") @PathVariable Integer id,
                    @ApiParam(required = true, value = "ノード情報") @RequestBody Map map) {
    return ActionHelper.responseOk(categoryTreeService.updateNode(treeId, id, map));
}

Service:

/**
 * 単一ノードを更新
 * @param treeId
 * @param nodeId
 * @param map
 * @return
 */
@Transactional
public JSON updateNode(Integer treeId, Integer nodeId, Map map) {
    JSONArray jsonTree = JSONArray.parseArray(categoryTreeRepository.selectJsonTree(treeId));
    if (!search(jsonTree, "id", nodeId, new JSONObject()).isEmpty()) {
        update(jsonTree, "id", nodeId, map);
        categoryTreeRepository.updateTree(jsonTree.toString(), treeId);
    } else {
        throw new BadRequestException("指定されたIDのノードが存在しません");
    }
    return jsonTree;
}

/**
 * 指定条件に一致するノードの属性を更新
 * @param jsonArray 更新対象のJSON配列
 * @param key       更新条件のキー
 * @param value     更新条件の値
 * @param map       更新する属性情報
 */
public void update(JSONArray jsonArray, String key, Object value, Map<String, Object> map) {
    for (int i = 0; i < jsonArray.size(); i++) {
        JSONObject json = jsonArray.getJSONObject(i);
        if (value.toString().equals(json.get(key).toString())) {
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                json.put(entry.getKey(), entry.getValue());
            }
            break;
        } else if (json.getJSONArray("children") != null) {
            update(json.getJSONArray("children"), key, value, map);
        }
    }
}

6月21日 21:56 投稿