Skip to content

Commit 85e7d07

Browse files
(feat) Preview json path in context
1 parent 1f6905f commit 85e7d07

File tree

12 files changed

+322
-57
lines changed

12 files changed

+322
-57
lines changed

demo/src/index.js

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,72 @@
1-
import React, {Component} from 'react'
2-
import {render} from 'react-dom'
1+
import React, {Component} from 'react';
2+
import {render} from 'react-dom';
33

4-
import JsonPathEditor from '../../src'
4+
import JsonPathEditor from '../../src';
5+
import Previewer from '../../src/components/JsonPathPreviewer';
56
import { generateRandomJson } from './generateRandomJson';
67

78

89
class Demo extends Component {
9-
constructor(props) {
10-
super(props)
10+
constructor(props) {
11+
super(props);
1112

12-
this.state = {
13-
json: generateRandomJson(5)
13+
this.state = {
14+
json: generateRandomJson(5)
15+
};
1416
}
15-
}
1617

17-
render() {
18-
const {json} = this.state;
19-
return <div>
20-
<h1>react-jsonpath-editor Demo</h1>
21-
<h2>With a default json</h2>
22-
<JsonPathEditor/>
23-
<h2>With a random json and a default value <button onClick={() => this.setState({json: generateRandomJson(5)})}>Click here to generate a new input json</button></h2>
24-
<JsonPathEditor value='$' json={json}/>
25-
<h2>With a default value</h2>
26-
<JsonPathEditor value='$.store.book'/>
27-
</div>
28-
}
18+
render() {
19+
const {json} = this.state;
20+
return <div>
21+
<h1>react-jsonpath-editor Demo</h1>
22+
<h2>With a default json</h2>
23+
<JsonPathEditor/>
24+
<h2>With a random json and a default value <button onClick={() => this.setState({json: generateRandomJson(5)})}>Click here to generate a new input json</button></h2>
25+
<JsonPathEditor value='$' json={json}/>
26+
<h2>With a default value</h2>
27+
<JsonPathEditor value='$.store.book'/>
28+
<br/>
29+
<br/>
30+
<br/>
31+
<Previewer json={{
32+
'store': {
33+
'book': [
34+
{
35+
'category': 'reference',
36+
'author': 'Nigel Rees',
37+
'title': 'Sayings of the Century',
38+
'price': 8.95
39+
},
40+
{
41+
'category': 'fiction',
42+
'author': 'Evelyn Waugh',
43+
'title': 'Sword of Honour',
44+
'price': 12.99
45+
},
46+
{
47+
'category': 'fiction',
48+
'author': 'Herman Melville',
49+
'title': 'Moby Dick',
50+
'isbn': '0-553-21311-3',
51+
'price': 8.99
52+
},
53+
{
54+
'category': 'fiction',
55+
'author': 'J. R. R. Tolkien',
56+
'title': 'The Lord of the Rings',
57+
'isbn': '0-395-19395-8',
58+
'price': 22.99
59+
}
60+
],
61+
'bicycle': {
62+
'color': 'red',
63+
'price': 19.95
64+
}
65+
}
66+
}} jsonPath='$.store' />
67+
68+
</div>;
69+
}
2970
}
3071

31-
render(<Demo/>, document.querySelector('#demo'))
72+
render(<Demo/>, document.querySelector('#demo'));

doczrc.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ import { css } from 'docz-plugin-css';
22
import { svgr } from 'docz-plugin-svgr';
33

44
export default {
5-
themeConfig: {
6-
mode: 'dark'
7-
},
85
plugins: [
96
svgr(),
107
css()

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
},
2323
"dependencies": {
2424
"easy-json-schema": "0.0.2-beta",
25-
"jsoneditor": "^5.16.0",
2625
"jsonpath": "^1.0.0",
2726
"prop-types": "^15.6.2"
2827
},

src/JsonPathPreviewer.mdx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
name: react-json-path-previewer
3+
route: /json-path-previewer
4+
---
5+
6+
import { Playground, PropsTable } from 'docz'
7+
import { JsonPathPreviewer } from './';
8+
9+
# React JSON Path previewer
10+
11+
<PropsTable of={JsonPathPreviewer} />
12+
13+
## Basic usage
14+
15+
<Playground>
16+
<JsonPathPreviewer
17+
jsonPath='$..author'
18+
json={{
19+
'store': {
20+
'book': [
21+
{
22+
'category': 'reference',
23+
'author': 'Nigel Rees',
24+
'title': 'Sayings of the Century',
25+
'price': 8.95
26+
},
27+
{
28+
'category': 'fiction',
29+
'author': 'Evelyn Waugh',
30+
'title': 'Sword of Honour',
31+
'price': 12.99
32+
},
33+
{
34+
'category': 'fiction',
35+
'author': 'Herman Melville',
36+
'title': 'Moby Dick',
37+
'isbn': '0-553-21311-3',
38+
'price': 8.99
39+
},
40+
{
41+
'category': 'fiction',
42+
'author': 'J. R. R. Tolkien',
43+
'title': 'The Lord of the Rings',
44+
'isbn': '0-395-19395-8',
45+
'price': 22.99
46+
}
47+
],
48+
'bicycle': {
49+
'color': 'red',
50+
'price': 19.95
51+
}
52+
}
53+
}}/>
54+
</Playground>

src/components/Editor.js

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import React, { Component } from 'react';
2-
import JSONEditor from 'jsoneditor';
32
import jp from 'jsonpath/jsonpath.min';
43
import ejs from 'easy-json-schema';
54
import { getSuggestions } from './suggestionBuilder';
@@ -8,6 +7,7 @@ import SuggestionList from './SuggestionList';
87

98
import 'jsoneditor/dist/jsoneditor.min.css';
109
import './editor.css';
10+
import JsonPathPreviewer from './JsonPathPreviewer';
1111

1212
const defaultJsonToFilter = {
1313
'store': {
@@ -51,7 +51,6 @@ class Editor extends Component {
5151
constructor(props) {
5252
super(props);
5353

54-
this.jsonEditorRef = React.createRef();
5554
this.onJsonChange = this.onJsonChange.bind(this);
5655
this.onSelectSuggestion = this.onSelectSuggestion.bind(this);
5756
this.onCaretChanged = this.onCaretChanged.bind(this);
@@ -64,10 +63,7 @@ class Editor extends Component {
6463
}
6564

6665
componentDidMount() {
67-
this.jsonEditor = new JSONEditor(this.jsonEditorRef.current, { modes: ['tree', 'code'], search: false, change: this.onJsonChange });
68-
this.jsonEditor.set(this.state.jsonToFilter);
6966
this.initSchema(this.props);
70-
this.jsonEditor.expandAll();
7167
this.evalPathAndSuggestions(this.props);
7268
this.props.input.addEventListener('keyup',this.onCaretChanged);
7369
this.props.input.addEventListener('click',this.onCaretChanged);
@@ -90,7 +86,6 @@ class Editor extends Component {
9086
initSchema(props) {
9187
if (props.jsonSchema) {
9288
this.setState({ jsonSchema: props.jsonSchema });
93-
this.jsonEditor.setSchema(props.jsonSchema);
9489
} else {
9590
const schema = ejs(this.state.jsonToFilter);
9691
this.setState({ jsonSchema: schema });
@@ -104,14 +99,10 @@ class Editor extends Component {
10499
this.setState({ jsonToFilter: newProps.json }, () => {
105100
resolve();
106101
});
107-
this.jsonEditor.set(newProps.json);
108102
});
109103

110104
promises.push(jsonToFilterStatePromise);
111105
}
112-
if (newProps.jsonSchema) {
113-
this.jsonEditor.setSchema(newProps.jsonSchema);
114-
}
115106

116107
// ensure state modifications to jsonToFilter did propagate
117108
if (promises.length > 0) {
@@ -134,20 +125,11 @@ class Editor extends Component {
134125
);
135126

136127
this.setState({ suggestions });
137-
138-
try {
139-
const filteredJson = jp.query(this.state.jsonToFilter, props.jsonpath);
140-
this.jsonEditor.set(filteredJson);
141-
this.jsonEditor.expandAll();
142-
} catch (e) {
143-
this.jsonEditor.set(this.state.jsonToFilter);
144-
this.jsonEditor.expandAll();
145-
}
146128
}
147129
}
148130

149131
onJsonChange() {
150-
this.setState({ jsonToFilter: this.jsonEditor.get() }, () => this.initSchema(this.props));
132+
this.initSchema(this.props);
151133
}
152134

153135
onSelectSuggestion(suggestion) {
@@ -174,15 +156,20 @@ class Editor extends Component {
174156
left: this.props.position.x,
175157
};
176158

159+
const {jsonpath} = this.props;
160+
const {jsonToFilter, suggestions} = this.state;
161+
177162
return <div className='react-json-path-editor-container' style={style} onMouseEnter={this.props.onMouseEnter} onMouseLeave={this.props.onMouseLeave}>
178163
<div className='react-json-path-editor-intellisense'>
179164
<SuggestionList
180-
suggestions={this.state.suggestions}
165+
suggestions={suggestions}
181166
onSelectSuggestion={this.onSelectSuggestion}
182167
// onSelectSuggestionToSimulate={}
183168
/>
184169
</div>
185-
<div className='react-json-path-editor-jsoneditor-container' ref={this.jsonEditorRef}></div>
170+
<div className='react-json-path-editor-jsoneditor-container'>
171+
<JsonPathPreviewer json={jsonToFilter} jsonPath={jsonpath}/>
172+
</div>
186173
</div>;
187174
}
188175
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.highlighted {
2+
color: green;
3+
font-weight: bold;
4+
}
5+
6+
code {
7+
color: #777;
8+
font: 12px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
9+
}
10+
11+
code p {
12+
margin: 0;
13+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import React, { Component } from 'react';
2+
import jp from 'jsonpath/jsonpath.min';
3+
import PropTypes from 'prop-types';
4+
import './JsonPathPreviewer.css';
5+
6+
export const carriageReturnTag = '£CR£';
7+
export const indentationIncrementationTag = '£INC£';
8+
export const indentationDecrementationTag = '£DEC£';
9+
export const highlightingTags = {
10+
start: '££TAGGED£',
11+
end: '£TAGGED££'
12+
};
13+
14+
class JsonPathPreviewer extends Component {
15+
16+
evalJsonPath(json, jsonPath) {
17+
try {
18+
return jp.paths(json, jsonPath);
19+
} catch(e) {
20+
return [];
21+
}
22+
}
23+
24+
tagPartOfJsonToHighlight(jsonAsObject, paths, traversedPath = ['$']) {
25+
if (Array.isArray(jsonAsObject)) {
26+
const doesTraversingPathMatch = paths.filter(oneOfPathsToRetrieve => oneOfPathsToRetrieve.join(',') === traversedPath.join(',')).length > 0;
27+
return `${carriageReturnTag + indentationIncrementationTag}${doesTraversingPathMatch ? highlightingTags.start : ''}[${carriageReturnTag + indentationIncrementationTag}${jsonAsObject.map((item, index) =>
28+
this.tagPartOfJsonToHighlight(item, paths, [...traversedPath, index])
29+
).join(',' + carriageReturnTag)}${indentationDecrementationTag + carriageReturnTag}]${doesTraversingPathMatch ? highlightingTags.end : ''}${indentationDecrementationTag}`;
30+
}
31+
32+
if (typeof jsonAsObject === 'object') {
33+
const doesTraversingPathMatch = paths.filter(oneOfPathsToRetrieve => oneOfPathsToRetrieve.join(',') === traversedPath.join(',')).length > 0;
34+
return `${doesTraversingPathMatch ? highlightingTags.start : ''}{${carriageReturnTag + indentationIncrementationTag}${Object.keys(jsonAsObject).map(key =>
35+
`"${key}": ${this.tagPartOfJsonToHighlight(jsonAsObject[key], paths, [...traversedPath, key])}`
36+
).join(',' + carriageReturnTag)}${indentationDecrementationTag + carriageReturnTag}}${doesTraversingPathMatch ? highlightingTags.end : ''}`;
37+
}
38+
39+
const doesTraversingPathMatch = paths.filter(oneOfPathsToRetrieve => oneOfPathsToRetrieve.join(',') === traversedPath.join(',')).length > 0;
40+
41+
if (typeof jsonAsObject === 'number') {
42+
return `${doesTraversingPathMatch ? highlightingTags.start : ''}${jsonAsObject}${doesTraversingPathMatch ? highlightingTags.end : ''}`;
43+
} else {
44+
return `"${doesTraversingPathMatch ? highlightingTags.start : ''}${jsonAsObject}${doesTraversingPathMatch ? highlightingTags.end : ''}"`;
45+
}
46+
}
47+
48+
convertTaggedJsonAsReactComponent(taggedJSON) {
49+
let increments = 0;
50+
let highlightBlock = false;
51+
return taggedJSON.split(carriageReturnTag).map(line => {
52+
if (line.includes(indentationIncrementationTag)) increments++;
53+
if (line.includes(highlightingTags.start + '[') || line.includes(highlightingTags.start + '{')) highlightBlock = true;
54+
const toReturn = <React.Fragment>
55+
<p className={highlightBlock ? 'highlighted' : ''}>
56+
{Array(increments).fill(<React.Fragment>&nbsp;</React.Fragment>)}
57+
{line.replace(new RegExp(indentationIncrementationTag,'g'), '').replace(new RegExp(indentationDecrementationTag,'g'), '')
58+
.split(highlightingTags.start).map(jsonPart => {
59+
const parts = jsonPart.split(highlightingTags.end);
60+
if (parts.length === 2) {
61+
return <React.Fragment><span className='highlighted'>{parts[0]}</span>{parts[1]}</React.Fragment>;
62+
}
63+
return <React.Fragment>{jsonPart}</React.Fragment>;
64+
})}
65+
</p>
66+
</React.Fragment>;
67+
if (line.includes(indentationDecrementationTag)) increments--;
68+
if (line.includes(']' + highlightingTags.end) || line.includes('}' + highlightingTags.end)) highlightBlock = false;
69+
return toReturn;
70+
});
71+
}
72+
73+
render() {
74+
const {json, jsonPath} = this.props;
75+
76+
const pathsEvaluated = this.evalJsonPath(json, jsonPath);
77+
78+
return (
79+
<code>
80+
{this.convertTaggedJsonAsReactComponent(this.tagPartOfJsonToHighlight(json, pathsEvaluated))}
81+
</code>
82+
);
83+
}
84+
}
85+
86+
JsonPathPreviewer.propTypes = {
87+
/** Json on which execute jsonPath */
88+
json: PropTypes.object.isRequired,
89+
/** JsonPath to preview on JSON */
90+
jsonPath: PropTypes.string.isRequired
91+
};
92+
93+
export default JsonPathPreviewer;

src/components/editor.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88

99
.react-json-path-editor-jsoneditor-container {
1010
width: 500px;
11+
max-height: 500px;
12+
overflow: auto;
1113
background: white;
14+
border: 1px solid #3883fa;
1215
box-shadow: 5px 5px 5px 1px rgba(0, 0, 0, .2);
1316
}
1417

0 commit comments

Comments
 (0)