uBlock/tools/jsonpath-tool.html
Raymond Hill faff035203
JSONPath: Add ability to select root node for appending/modifying
As discussed with filter list maintainers.

Examples of usage:

  $ => result is `null`
  $+={"modifyOrCreate": "..."}

These expressions were not working with previous version.
2025-08-09 13:56:02 -04:00

197 lines
5 KiB
HTML

<!DOCTYPE html>
<!--
Requires a local server in root of uBlock repo:
python3 -m http.server
Then load the following URL in the browser:
http://localhost:8000/tools/jsonpath-tool.html
-->
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>JSONPath tool</title>
<style>
body {
box-sizing: border-box;
display: flex;
flex-direction: column;
font-size: 16px;
gap: 0;
height: 100vh;
margin: 0;
padding: 0.5em;
width: 100vw;
}
h2 {
margin: 0;
}
main {
box-sizing: border-box;
display: flex;
flex-direction: column;
flex-grow: 1;
gap: 0.5em;
}
section {
box-sizing: border-box;
display: flex;
gap: 0.5em;
max-height: 80cqh;
overflow: auto;
}
section > * {
border: 1px solid gray;
box-sizing: border-box;
flex-grow: 1;
font-family: monospace;
font-size: small;
}
section .cm-lineWrapping {
word-break: break-all !important;
}
#jsondata-in {
min-height: 50vh;
resize: vertical;
}
#jsonpath-input {
font-size: medium;
}
#jsonpath-result {
background-color: #eee;
flex-grow: 1;
white-space: pre-wrap;
}
</style>
</head>
<body>
<h2>uBO-flavored JSONPath tool</h2>
<main>
<section>
</section>
<input id="jsonpath-input" placeholder="JSON path expression" spellcheck="false" value="$..book[?@.price<10]" />
<div id="jsonpath-result">&nbsp;</div>
</main>
<script src="../platform/mv3/extension/lib/codemirror/codemirror-ubol/dist/cm6.bundle.ubol.min.js"></script>
<script type="module">
// Requires a local server in root of repo:
// python3 -m http.server
//
// Then load the following URL in the browser:
// http://localhost:8000/tools/jsonpath-tool.html
import { JSONPath } from '../src/js/jsonpath.js';
let aLastText = JSON.stringify({
store: {
book: [ {
category: "reference",
author: "Nigel Rees",
title: "Sayings of the Century",
price: 8.95
}, {
category: "fiction",
author: "Evelyn Waugh",
title: "Sword of Honour",
price: 12.99
}, {
category: "fiction",
author: "Herman Melville",
title: "Moby Dick",
isbn: "0-553-21311-3",
price: 8.99
}, {
category: "fiction",
author: "J. R. R. Tolkien",
title: "The Lord of the Rings",
isbn: "0-395-19395-8",
price: 22.99
} ],
bicycle: {
color: "red",
price: 19.95
}
}
}, null, 2);
function readJSON() {
const textarea = document.querySelector('#jsondata-in');
let data;
try {
const text = cmMergeView.a.state.doc.toString();
data = JSON.parse(text);
} catch {
data = {};
}
if ( typeof data !== 'object' || data === null ) {
data = {};
}
return data;
}
function formatResult(a) {
if ( a === undefined ) { return 'undefined'; }
if ( jsonp.valid === false ) { return 'bad expression'; }
if ( Array.isArray(a) === false ) {
return JSON.stringify(a, null, 2);
}
const out = [];
for ( const i of a ) {
out.push(`[ ${i.map(j => JSON.stringify(j)).join(', ')} ]`);
}
return out.join('\n');
}
function process() {
const input = document.querySelector('#jsonpath-input');
const jsonpath = input.value;
jsonp.compile(jsonpath);
const jsonDataIn = readJSON();
const left = formatResult(jsonp.evaluate(jsonDataIn));
const pathsDiv = document.querySelector('#jsonpath-result');
pathsDiv.textContent = left;
const jsonDataOut = readJSON();
const objAfter = jsonp.apply(jsonDataOut);
const bText = JSON.stringify(objAfter !== undefined ? objAfter : jsonDataOut, null, 2);
cmMergeView.b.dispatch({
changes: {
from: 0, to: cmMergeView.b.state.doc.length,
insert: bText,
},
});
}
const jsonp = new JSONPath();
let jsonDataIn = {};
let processTimer;
const cmMergeView = self.cm6.createMergeView({
aDoc: aLastText,
aUpdateListener: info => {
if ( info.docChanged === false ) { return; }
if ( processTimer !== undefined ) { return; }
processTimer = setTimeout(( ) => {
processTimer = undefined;
const aNewText = info.state.doc.toString();
if ( aNewText === aLastText ) { return; }
aLastText = aNewText;
process();
}, 71);
},
}, document.querySelector('section'));
{
const input = document.querySelector('#jsonpath-input');
input.addEventListener('input', ( ) => {
process();
});
}
process();
</script>
</body>
</html>