From 74bb71b1abd35c46a51399c46c48a772bd153e9d Mon Sep 17 00:00:00 2001 From: Tyler Mairose Date: Tue, 12 Nov 2024 10:29:30 -0500 Subject: [PATCH] Add json path evaluator --- navbar.js | 8 ++ package-lock.json | 99 ++++++++++++++ package.json | 4 + src/pages/util/Expressions.js | 71 ++++++++++ src/pages/util/json-path-evaluator.js | 181 ++++++++++++++++++++++++++ src/pages/util/json-path.module.css | 8 ++ src/pages/util/sample.json | 38 ++++++ 7 files changed, 409 insertions(+) create mode 100644 src/pages/util/Expressions.js create mode 100644 src/pages/util/json-path-evaluator.js create mode 100644 src/pages/util/json-path.module.css create mode 100644 src/pages/util/sample.json diff --git a/navbar.js b/navbar.js index 6d7803f3d..a31f8ba84 100644 --- a/navbar.js +++ b/navbar.js @@ -25,6 +25,14 @@ module.exports = { {label: 'NERM', to: '/docs/api/nerm/v1'}, ], }, + { + type: 'dropdown', + label: 'Tools', + position: 'left', + items: [ + {label: 'Json Path Evaluator', to: '/util/json-path-evaluator'}, + ], + }, { type: 'dropdown', label: 'Community', diff --git a/package-lock.json b/package-lock.json index 38f817426..07cbce14e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@gracefullight/docusaurus-plugin-microsoft-clarity": "^1.0.0", "@mdx-js/react": "^3.0.0", "@typeform/embed-react": "^1.21.0", + "ace-builds": "^1.36.4", "autoprefixer": "^10.4.13", "classnames": "^2.3.2", "clsx": "^2.0.0", @@ -25,9 +26,12 @@ "docusaurus-theme-openapi-docs": "^4.0.1", "docusaurus2-dotenv": "^1.4.0", "esbuild-loader": "^2.20.0", + "jsonpath-plus": "^10.1.0", + "jsonpathly": "^2.0.1", "ldrs": "^1.0.1", "prism-react-renderer": "^2.3.0", "react": "^18.2.0", + "react-ace": "^13.0.0", "react-dom": "^18.2.0", "react-live": "^4.0.0", "react-markdown": "^8.0.7", @@ -3879,6 +3883,28 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -5032,6 +5058,11 @@ "node": ">= 0.6" } }, + "node_modules/ace-builds": { + "version": "1.36.4", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.36.4.tgz", + "integrity": "sha512-eE+iAsLRfNsq30yd34cezKSob6/N9mQatWs44Bp5LUDgKZ3rJtQds/YtcbnwbEWMTe7yCIxG/Cfezd4BsKIiFg==" + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -5278,6 +5309,14 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/antlr4": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.13.2.tgz", + "integrity": "sha512-QiVbZhyy4xAZ17UPEuG3YTOt8ZaoeOR1CvEAqrEsDBsOqINslaB147i9xqljZqoyf5S+EUlGStaj+t22LT9MOg==", + "engines": { + "node": ">=16" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -7725,6 +7764,11 @@ "node": ">=0.3.1" } }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, "node_modules/diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -10941,6 +10985,14 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "engines": { + "node": ">= 10.16.0" + } + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -11018,6 +11070,32 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonpath-plus": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.1.0.tgz", + "integrity": "sha512-gHfV1IYqH8uJHYVTs8BJX1XKy2/rR93+f8QQi0xhx95aCiXn1ettYAd5T+7FU6wfqyDoX/wy0pm/fL3jOKJ9Lg==", + "dependencies": { + "@jsep-plugin/assignment": "^1.2.1", + "@jsep-plugin/regex": "^1.0.3", + "jsep": "^1.3.9" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/jsonpathly": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jsonpathly/-/jsonpathly-2.0.1.tgz", + "integrity": "sha512-vme/uKIxIwwttk8w1HOM9pLzMSz8Kwm8QaoKNEvZtUI8rqwdfccHaS8eggGr0DsK++W5PBAF58X31IUCZgtRLA==", + "dependencies": { + "antlr4": "^4.13.1", + "fast-deep-equal": "^3.1.3" + } + }, "node_modules/katex": { "version": "0.16.11", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz", @@ -11191,6 +11269,11 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -21557,6 +21640,22 @@ "node": ">=0.10.0" } }, + "node_modules/react-ace": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-13.0.0.tgz", + "integrity": "sha512-PPk2O/ArHzDtbnK82QImfHYXwuiitRgHJf5AxwMQh9zciojbWsPmKJm1tMgWOYLCtGEz8/Dh3MxRxrXe7QcstQ==", + "dependencies": { + "ace-builds": "^1.36.3", + "diff-match-patch": "^1.0.5", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", diff --git a/package.json b/package.json index 08eba910f..4db581df6 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@gracefullight/docusaurus-plugin-microsoft-clarity": "^1.0.0", "@mdx-js/react": "^3.0.0", "@typeform/embed-react": "^1.21.0", + "ace-builds": "^1.36.4", "autoprefixer": "^10.4.13", "classnames": "^2.3.2", "clsx": "^2.0.0", @@ -38,9 +39,12 @@ "docusaurus-theme-openapi-docs": "^4.0.1", "docusaurus2-dotenv": "^1.4.0", "esbuild-loader": "^2.20.0", + "jsonpath-plus": "^10.1.0", + "jsonpathly": "^2.0.1", "ldrs": "^1.0.1", "prism-react-renderer": "^2.3.0", "react": "^18.2.0", + "react-ace": "^13.0.0", "react-dom": "^18.2.0", "react-live": "^4.0.0", "react-markdown": "^8.0.7", diff --git a/src/pages/util/Expressions.js b/src/pages/util/Expressions.js new file mode 100644 index 000000000..27bcabef5 --- /dev/null +++ b/src/pages/util/Expressions.js @@ -0,0 +1,71 @@ +import React from 'react'; + +function JsonExpressions() { + return ( +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
JSONPathDescription
$the root object/element
@the current object/element
. or []child operator
..recursive descent. JSONPath borrows this syntax from E4X.
*wildcard. All objects/elements regardless their names.
[] + subscript operator. XPath uses it to iterate over element collections and for predicates. In + Javascript and JSON it is the native array operator. +
[,] + Union operator in XPath results in a combination of node sets. JSONPath allows alternate + names or array indices as a set. +
[start:end:step]array slice operator borrowed from ES4.
?()applies a filter (script) expression.
()script expression, using the underlying script engine.
+ + See: JSONPath expressions - https://goessner.net/ + +
+
+ ); +} + +export default JsonExpressions; \ No newline at end of file diff --git a/src/pages/util/json-path-evaluator.js b/src/pages/util/json-path-evaluator.js new file mode 100644 index 000000000..e2cc2a62e --- /dev/null +++ b/src/pages/util/json-path-evaluator.js @@ -0,0 +1,181 @@ +import React from 'react'; +import clsx from 'clsx'; +import Link from '@docusaurus/Link'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import Layout from '@theme/Layout'; +import BlogBanner from '../components/blog/BlogBanner'; + +import styles from './json-path.module.css'; +import useBaseUrl from '@docusaurus/useBaseUrl'; + +import './json-path.module.css'; + +import * as jp from "jsonpathly"; + +import JsonExpressions from './Expressions'; +import Sample from './sample.json'; + +import AceEditor from 'react-ace'; +import 'ace-builds/src-noconflict/mode-json'; +import 'ace-builds/src-noconflict/theme-solarized_dark'; +import 'ace-builds/src-noconflict/ext-language_tools.js'; + +// Ensure `ace` is defined before accessing its configuration +if (typeof ace !== 'undefined' && ace.config) { + ace.config.setModuleUrl( + 'ace/mode/json_worker', + new URL('ace-builds/src-noconflict/worker-json.js', 'https://ajaxorg.github.io/').toString() + ); +} + +export default function JsonPathEvaluator() { + + const [inputJson, setInputJson] = React.useState(JSON.stringify(Sample, null, 4)); + const [result, setResult] = React.useState(JSON.stringify([], null, 4)); + const [resultType, setResultType] = React.useState<'value' | 'path'>('value'); + const [query, setQuery] = React.useState('$.requestedItemsStatus[?(@.name in ["Engineering Access"])]'); + const [isQueryValid, setQueryValid] = React.useState(true); + const [queryParseError, setQueryParseError] = React.useState(''); + + const queryInput = React.useRef(null); + + function onChangeJson(input) { + setInputJson(input); + } + + function onInputQuery(event) { + const inputQuery = event.target.value; + setQuery(inputQuery); + } + + function onChangeResultType(event) { + const type = event.target.checked ? 'path' : 'value'; + setResultType(type); + } + + function applyJsonPath(jsonStr, jsonPath) { + let json = ''; + let result = ''; + + try { + json = JSON.parse(jsonStr.replace(/(\r\n|\n|\r)/gm, '')); + } catch (error) { + setResult('JSON Parse Error'); + return; + } + + try { + result = jp.query(json, jsonPath); + setQueryValid(true); + setQueryParseError(''); + } catch (error) { + console.log(error) + setQueryValid(false); + if (error instanceof Error) { + setQueryParseError(error.message); + } + } + + console.log(result) + + if (0 < result?.length) { + setResult(JSON.stringify(result, undefined, 2)); + } else { + setResult('No match'); + } + } + + React.useEffect(() => { + applyJsonPath(inputJson, query); + }); + + React.useEffect(() => { + queryInput.current?.focus(); + }, []); + + return ( +
+
+ + +
+ {queryParseError} +
+
+ +
+
+
+ + +
+
+ +
+ +
+
+ + {/* */} + +
+
+

Inputs

+ +
+ +
+

Evaluation Results

+ +
+
+
+ ); +} diff --git a/src/pages/util/json-path.module.css b/src/pages/util/json-path.module.css new file mode 100644 index 000000000..36d7b9f67 --- /dev/null +++ b/src/pages/util/json-path.module.css @@ -0,0 +1,8 @@ +.editor { + height: 500px; + } + +#jsonpath-query { + width: 500px; + height: 2em; +} \ No newline at end of file diff --git a/src/pages/util/sample.json b/src/pages/util/sample.json new file mode 100644 index 000000000..145527858 --- /dev/null +++ b/src/pages/util/sample.json @@ -0,0 +1,38 @@ +{ + "accessRequestId": "2c91808b6ef1d43e016efba0ce470904", + "requestedFor": { + "type": "IDENTITY", + "id": "2c91808568c529c60168cca6f90c1313", + "name": "William Wilson" + }, + "requestedItemsStatus": [ + { + "id": "2c91808b6ef1d43e016efba0ce470904", + "name": "Engineering Access", + "description": "Access to engineering database", + "type": "ACCESS_PROFILE", + "operation": "Add", + "comment": "William needs this access to do his job.", + "clientMetadata": { + "applicationName": "My application" + }, + "approvalInfo": [ + { + "approvalComment": "This access looks good. Approved.", + "approvalDecision": "APPROVED", + "approverName": "Stephen.Austin", + "approver": { + "type": "IDENTITY", + "id": "2c91808568c529c60168cca6f90c1313", + "name": "William Wilson" + } + } + ] + } + ], + "requestedBy": { + "type": "IDENTITY", + "id": "2c91808568c529c60168cca6f90c1313", + "name": "William Wilson" + } + } \ No newline at end of file