11 Commits

42 changed files with 2036 additions and 522 deletions

View File

@ -3,7 +3,7 @@
<option name="channel" value="DEFAULT" /> <option name="channel" value="DEFAULT" />
<option name="command" value="test --package jsonpath_lib" /> <option name="command" value="test --package jsonpath_lib" />
<option name="allFeatures" value="false" /> <option name="allFeatures" value="false" />
<option name="nocapture" value="false" /> <option name="nocapture" value="true" />
<option name="backtrace" value="SHORT" /> <option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" /> <option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs /> <envs />

12
.idea/runConfigurations/selector.xml generated Normal file
View File

@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="selector" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="test --package jsonpath_lib --test selector &quot;&quot;" />
<option name="allFeatures" value="false" />
<option name="nocapture" value="false" />
<option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@ -3,7 +3,7 @@
<option name="channel" value="DEFAULT" /> <option name="channel" value="DEFAULT" />
<option name="command" value="test --package jsonpath_lib --test serde &quot;&quot;" /> <option name="command" value="test --package jsonpath_lib --test serde &quot;&quot;" />
<option name="allFeatures" value="false" /> <option name="allFeatures" value="false" />
<option name="nocapture" value="true" /> <option name="nocapture" value="false" />
<option name="backtrace" value="SHORT" /> <option name="backtrace" value="SHORT" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" /> <option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs /> <envs />

View File

@ -1,6 +1,6 @@
[package] [package]
name = "jsonpath_lib" name = "jsonpath_lib"
version = "0.1.8" version = "0.1.9"
authors = ["Changseok Han <freestrings@gmail.com>"] authors = ["Changseok Han <freestrings@gmail.com>"]
description = "JsonPath in Rust and Webassembly - Webassembly Demo: https://freestrings.github.io/jsonpath" description = "JsonPath in Rust and Webassembly - Webassembly Demo: https://freestrings.github.io/jsonpath"

580
README.md
View File

@ -5,51 +5,285 @@
![Crates.io](https://img.shields.io/crates/d/jsonpath_lib.svg?label=%60jsonpath_lib%60%20crates.io%20downloads) ![Crates.io](https://img.shields.io/crates/d/jsonpath_lib.svg?label=%60jsonpath_lib%60%20crates.io%20downloads)
![npm](https://img.shields.io/npm/dt/jsonpath-rs.svg?label=%60jsonpath-rs%60%20npm%20downloads) ![npm](https://img.shields.io/npm/dt/jsonpath-rs.svg?label=%60jsonpath-rs%60%20npm%20downloads)
`Rust` 버전 [JsonPath](https://goessner.net/articles/JsonPath/) 구현이다. `Webassembly``Javascript`에서도 역시 동일한 API 인터페이스를 제공 한다. `Rust` 버전 [JsonPath](https://goessner.net/articles/JsonPath/) 구현이다. `Webassembly``Javascript`에서도 유사한 API 인터페이스를 제공 한다.
It is an implementation for [JsonPath](https://goessner.net/articles/JsonPath/) written in `Rust`. it provide the same API interface in `Webassembly` and` Javascript` also. It is an implementation for [JsonPath](https://goessner.net/articles/JsonPath/) written in `Rust`. it provide a similar API interface in `Webassembly` and` Javascript` also.
- [Webassembly Demo](https://freestrings.github.io/jsonpath/) - [Webassembly Demo](https://freestrings.github.io/jsonpath/)
- [Rust documentation](https://docs.rs/jsonpath_lib/0.1.6/jsonpath_lib) - [Rust documentation](https://docs.rs/jsonpath_lib)
## Why? ## Why?
To enjoy Rust! To enjoy Rust!
## API ## Rust API
[With Javascript](#with-javascript) - [jsonpath_lib crate](#jsonpath_lib-crate)
- [Rust - jsonpath::Selector struct](#rust---jsonpathselector-struct)
- [Rust - jsonpath::select(json: &serde_json::value::Value, jsonpath: &str)](#rust---jsonpathselectjson-serde_jsonvaluevalue-jsonpath-str)
- [Rust - jsonpath::select_as_str(json_str: &str, jsonpath: &str)](#rust---jsonpathselect_as_strjson-str-jsonpath-str)
- [Rust - jsonpath::select_as\<T: `serde::de::DeserializeOwned`\>(json_str: &str, jsonpath: &str)](#rust---jsonpathselect_ast-serdededeserializeownedjson-str-jsonpath-str)
- [Rust - jsonpath::compile(jsonpath: &str)](#rust---jsonpathcompilejsonpath-str)
- [Rust - jsonpath::selector(json: &serde_json::value::Value)](#rust---jsonpathselectorjson-serde_jsonvaluevalue)
- [Rust - jsonpath::selector_as\<T: `serde::de::DeserializeOwned`\>(json: &serde_json::value::Value)](#rust---jsonpathselector_ast-serdededeserializeownedjson-serde_jsonvaluevalue)
- [Rust - examples](https://github.com/freestrings/jsonpath/wiki/rust-examples)
- [jsonpath-wasm library](#jsonpath-wasm-library) ## Javascript API
- [jsonpath-rs library](#jsonpath-rs-library-only-nodejs)
- [javascript - jsonpath.select(json: string|object, jsonpath: string)](#javascript---jsonpathselectjson-stringobject-jsonpath-string)
- [javascript - jsonpath.compile(jsonpath: string)](#javascript---jsonpathcompilejsonpath-string)
- [javascript - jsonpath.selector(json: string|object)](#javascript---jsonpathselectorjson-stringobject)
- [javascript - alloc_json, dealloc_json](#javascript---alloc_json-dealloc_json)
- [javascript-wasm - examples](https://github.com/freestrings/jsonpath/wiki/Javascript-examples)
[With Rust (as library)](#with-rust-as-library) - [npm package](#npm-package)
- [Javascript - jsonpath.Selector class](#javascript---selector-class)
- [Javascript - jsonpath.select(json: string|object, jsonpath: string)](#javascript---jsonpathselectjson-stringobject-jsonpath-string)
- [Javascript - jsonpath.compile(jsonpath: string)](#javascript---jsonpathcompilejsonpath-string)
- [Javascript - jsonpath.selector(json: string|object)](#javascript---jsonpathselectorjson-stringobject)
- [Javascript - allocJson, deallocJson (Webassembly Only)](#javascript---allocjson-deallocjson-webassembly-only)
- [Javascript - examples](https://github.com/freestrings/jsonpath/wiki/Javascript-examples)
- [jsonpath_lib library](#jsonpath_lib-library) ## Simple time check
- [rust - jsonpath::select(json: &serde_json::value::Value, jsonpath: &str)](#rust---jsonpathselectjson-serde_jsonvaluevalue-jsonpath-str) - [jsonpath-wasm](https://github.com/freestrings/jsonpath/wiki/Simple-timecheck---jsonpath-wasm)
- [rust - jsonpath::select_as_str(json_str: &str, jsonpath: &str)](#rust---jsonpathselect_as_strjson-str-jsonpath-str) - [jsonpath-rs](https://github.com/freestrings/jsonpath/wiki/Simple-timecheck-jsonpath-native)
- [rust - jsonpath::select_as\<T: `serde::de::DeserializeOwned`\>(json_str: &str, jsonpath: &str)](#rust---jsonpathselect_ast-serdededeserializeownedjson-str-jsonpath-str)
- [rust - jsonpath::compile(jsonpath: &str)](#rust---jsonpathcompilejsonpath-str)
- [rust - jsonpath::selector(json: &serde_json::value::Value)](#rust---jsonpathselectorjson-serde_jsonvaluevalue)
- [rust - jsonpath::selector_as\<T: `serde::de::DeserializeOwned`\>(json: &serde_json::value::Value)](#rust---jsonpathselector_ast-serdededeserializeownedjson-serde_jsonvaluevalue)
- [rust - examples](https://github.com/freestrings/jsonpath/wiki/rust-examples)
[With AWS API Gateway](#) ---
[Simple time check - webassembly](https://github.com/freestrings/jsonpath/wiki/Simple-timecheck---jsonpath-wasm) ### Rust API
[Simple time check - native addon for NodeJs](https://github.com/freestrings/jsonpath/wiki/Simple-timecheck-jsonpath-native) #### jsonpath_lib crate
[Go to creates.io](https://crates.io/crates/jsonpath_lib)
## With Javascript ```rust
extern crate jsonpath_lib as jsonpath;
#[macro_use]
extern crate serde_json;
```
### jsonpath-wasm library #### Rust - jsonpath::Selector struct
```rust
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Friend {
name: String,
age: Option<u8>,
}
let json_obj = json!({
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]});
let mut selector = Selector::new();
let result = selector
.path("$..[?(@.age >= 30)]").unwrap()
// .value_from_str(&serde_json::to_string(&json_obj).unwrap() /*&str*/).unwrap()
// .value_from(&json_obj /*&impl serde::ser::Serialize*/).unwrap()
.value((&json_obj /*serde_json::value::Value*/ ).into()).unwrap()
.select_to_value().unwrap();
assert_eq!(json!([{"name": "친구3", "age": 30}]), result);
let result = selector.select_to_str().unwrap();
assert_eq!(r#"[{"name":"친구3","age":30}]"#, result);
let result = selector.select_to::<Vec<Friend>>().unwrap();
assert_eq!(vec![Friend { name: "친구3".to_string(), age: Some(30) }], result);
```
#### Rust - jsonpath::select(json: &serde_json::value::Value, jsonpath: &str)
```rust
let json_obj = json!({
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]});
let json = jsonpath::select(&json_obj, "$..friends[0]").unwrap();
let ret = json!([
{"name": "친구3", "age": 30},
{"name": "친구1", "age": 20}
]);
assert_eq!(json, ret);
```
#### Rust - jsonpath::select_as_str(json: &str, jsonpath: &str)
```rust
let ret = jsonpath::select_as_str(r#"
{
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]
}
"#, "$..friends[0]").unwrap();
assert_eq!(ret, r#"[{"name":"친구3","age":30},{"name":"친구1","age":20}]"#);
```
#### Rust - jsonpath::select_as\<T: `serde::de::DeserializeOwned`\>(json: &str, jsonpath: &str)
```rust
#[derive(Deserialize, PartialEq, Debug)]
struct Person {
name: String,
age: u8,
phones: Vec<String>,
}
let ret: Person = jsonpath::select_as(r#"
{
"person":
{
"name": "Doe John",
"age": 44,
"phones": [
"+44 1234567",
"+44 2345678"
]
}
}
"#, "$.person").unwrap();
let person = Person {
name: "Doe John".to_string(),
age: 44,
phones: vec!["+44 1234567".to_string(), "+44 2345678".to_string()],
};
assert_eq!(person, ret);
```
#### Rust - jsonpath::compile(jsonpath: &str)
```rust
let mut template = jsonpath::compile("$..friends[0]");
let json_obj = json!({
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]});
let json = template(&json_obj).unwrap();
let ret = json!([
{"name": "친구3", "age": 30},
{"name": "친구1", "age": 20}
]);
assert_eq!(json, ret);
```
#### Rust - jsonpath::selector(json: &serde_json::value::Value)
```rust
let json_obj = json!({
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]});
let mut selector = jsonpath::selector(&json_obj);
let json = selector("$..friends[0]").unwrap();
let ret = json!([
{"name": "친구3", "age": 30},
{"name": "친구1", "age": 20}
]);
assert_eq!(json, ret);
let json = selector("$..friends[1]").unwrap();
let ret = json!([
{"name": "친구4"},
{"name": "친구2", "age": 20}
]);
assert_eq!(json, ret);
```
#### Rust - jsonpath::selector_as\<T: `serde::de::DeserializeOwned`\>(json: &serde_json::value::Value)
```rust
let json_obj = json!({
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]});
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Friend {
name: String,
age: Option<u8>,
}
let mut selector = jsonpath::selector_as::<Vec<Friend>>(&json_obj);
let json = selector("$..friends[0]").unwrap();
let ret = vec!(
Friend { name: "친구3".to_string(), age: Some(30) },
Friend { name: "친구1".to_string(), age: Some(20) }
);
assert_eq!(json, ret);
let json = selector("$..friends[1]").unwrap();
let ret = vec!(
Friend { name: "친구4".to_string(), age: None },
Friend { name: "친구2".to_string(), age: Some(20) }
);
assert_eq!(json, ret);
```
---
### Javascript API
#### npm package
##### jsonpath-wasm (Not yet published)
*(not yet published `jsonpath-wasm`)*
```javascript ```javascript
// browser // browser
import * as jsonpath from "jsonpath-wasm"; import * as jsonpath from "jsonpath-wasm";
@ -57,15 +291,92 @@ import * as jsonpath from "jsonpath-wasm";
const jsonpath = require('jsonpath-wasm'); const jsonpath = require('jsonpath-wasm');
``` ```
### jsonpath-rs library (Only NodeJS) ##### jsonpath-rs (NodeJS only)
`jsonpath-rs` is native addon for NodeJs [Goto npmjs.org](https://www.npmjs.com/package/jsonpath-rs)
```javascript ```javascript
const jsonpath = require('jsonpath-rs'); const jsonpath = require('jsonpath-rs');
``` ```
### javascript - jsonpath.select(json: string|object, jsonpath: string) #### javascript - Selector class
##### jsonpath-wasm
`wasm-bindgen` 리턴타입 제약 때문에 빌더 패턴은 지원하지 않는다.
It does not support `builder-pattern` due to the `return type` restriction of `wasm-bindgen`.
```javascript
let jsonObj = {
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]
};
let ret = [
{"name": "친구3", "age": 30},
{"name": "친구1", "age": 20}
];
let selector = new jsonpath.Selector();
selector.path('$..friends[0]');
selector.value(jsonObj);
let selectToObj = selector.selectTo();
let selectToString = selector.selectToStr();
console.log(
JSON.stringify(ret) == JSON.stringify(selectToObj),
JSON.stringify(ret) == selectToString
);
// => true, true
```
##### jsonpath-rs
```javascript
let jsonObj = {
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]
};
let ret = [
{"name": "친구3", "age": 30},
{"name": "친구1", "age": 20}
];
let selector = new jsonpath.Selector()
.path('$..friends[0]')
.value(jsonObj);
let selectToObj = selector.selectTo();
let selectToString = selector.selectToStr();
console.log(
JSON.stringify(ret) == JSON.stringify(selectToObj),
JSON.stringify(ret) == selectToString
);
// => true, true
```
#### Javascript - jsonpath.select(json: string|object, jsonpath: string)
```javascript ```javascript
let jsonObj = { let jsonObj = {
@ -98,7 +409,7 @@ console.log(
// => true, true // => true, true
``` ```
### javascript - jsonpath.compile(jsonpath: string) #### Javascript - jsonpath.compile(jsonpath: string)
```javascript ```javascript
let template = jsonpath.compile('$..friends[0]'); let template = jsonpath.compile('$..friends[0]');
@ -157,7 +468,7 @@ console.log(
// => true, true // => true, true
``` ```
### javascript - jsonpath.selector(json: string|object) #### Javascript - jsonpath.selector(json: string|object)
```javascript ```javascript
let jsonObj = { let jsonObj = {
@ -198,13 +509,10 @@ console.log(
// => true, true // => true, true
``` ```
### javascript - alloc_json, dealloc_json #### Javascript - allocJson, deallocJson (Webassembly Only)
wasm-bindgen은 Javascript와 Webassembly간 값을 주고받을 때 JSON 객체는 String으로 변환되기 때문에, 반복해서 사용되는 JSON 객체는 Webassembly 영역에 생성해 두면 성능에 도움이 된다.
*(not supported in `jsonpath-rs`)* Since wasm-bindgen converts JSON objects to String when exchanging values between Javascript and Webassembly, creating frequently used JSON objects in the WebAssembly area helps performance.
wasm-bindgen은 Javascript와 Webassembly 간 값을 주고받을 때 JSON 객체는 String으로 변환되기 때문에, 반복해서 사용되는 JSON 객체를 Webassembly 영역에 생성해 두면 성능에 도움이 된다.
Since wasm-bindgen converts JSON objects to String when exchanging values between Javascript and Webassembly, it is helpful to create repeated Json objects in Webassembly area.
```javascript ```javascript
const jsonpath = require('@nodejs/jsonpath-wasm'); const jsonpath = require('@nodejs/jsonpath-wasm');
@ -223,7 +531,7 @@ let jsonObj = {
}; };
// allocate jsonObj in webassembly // allocate jsonObj in webassembly
let ptr = jsonpath.alloc_json(jsonObj); let ptr = jsonpath.allocJson(jsonObj);
// `0` is invalid pointer // `0` is invalid pointer
if(ptr == 0) { if(ptr == 0) {
@ -254,197 +562,5 @@ console.log(
// => true true true true true // => true true true true true
jsonpath.dealloc_json(ptr); jsonpath.deallocJson(ptr);
```
## With Rust (as library)
### jsonpath_lib library
```rust
extern crate jsonpath_lib as jsonpath;
#[macro_use]
extern crate serde_json;
```
### rust - jsonpath::select(json: &serde_json::value::Value, jsonpath: &str)
```rust
let json_obj = json!({
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]});
let json = jsonpath::select(&json_obj, "$..friends[0]").unwrap();
let ret = json!([
{"name": "친구3", "age": 30},
{"name": "친구1", "age": 20}
]);
assert_eq!(json, ret);
```
### rust - jsonpath::select_as_str(json: &str, jsonpath: &str)
```rust
let ret = jsonpath::select_as_str(r#"
{
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]
}
"#, "$..friends[0]").unwrap();
assert_eq!(ret, r#"[{"name":"친구3","age":30},{"name":"친구1","age":20}]"#);
```
### rust - jsonpath::select_as\<T: `serde::de::DeserializeOwned`\>(json: &str, jsonpath: &str)
```rust
#[derive(Deserialize, PartialEq, Debug)]
struct Person {
name: String,
age: u8,
phones: Vec<String>,
}
let ret: Person = jsonpath::select_as(r#"
{
"person":
{
"name": "Doe John",
"age": 44,
"phones": [
"+44 1234567",
"+44 2345678"
]
}
}
"#, "$.person").unwrap();
let person = Person {
name: "Doe John".to_string(),
age: 44,
phones: vec!["+44 1234567".to_string(), "+44 2345678".to_string()],
};
assert_eq!(person, ret);
```
### rust - jsonpath::compile(jsonpath: &str)
```rust
let mut template = jsonpath::compile("$..friends[0]");
let json_obj = json!({
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]});
let json = template(&json_obj).unwrap();
let ret = json!([
{"name": "친구3", "age": 30},
{"name": "친구1", "age": 20}
]);
assert_eq!(json, ret);
```
### rust - jsonpath::selector(json: &serde_json::value::Value)
```rust
let json_obj = json!({
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]});
let mut selector = jsonpath::selector(&json_obj);
let json = selector("$..friends[0]").unwrap();
let ret = json!([
{"name": "친구3", "age": 30},
{"name": "친구1", "age": 20}
]);
assert_eq!(json, ret);
let json = selector("$..friends[1]").unwrap();
let ret = json!([
{"name": "친구4"},
{"name": "친구2", "age": 20}
]);
assert_eq!(json, ret);
```
### rust - jsonpath::selector_as\<T: `serde::de::DeserializeOwned`\>(json: &serde_json::value::Value)
```rust
let json_obj = json!({
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]});
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Friend {
name: String,
age: Option<u8>,
}
let mut selector = jsonpath::selector_as::<Vec<Friend>>(&json_obj);
let json = selector("$..friends[0]").unwrap();
let ret = vec!(
Friend { name: "친구3".to_string(), age: Some(30) },
Friend { name: "친구1".to_string(), age: Some(20) }
);
assert_eq!(json, ret);
let json = selector("$..friends[1]").unwrap();
let ret = vec!(
Friend { name: "친구4".to_string(), age: None },
Friend { name: "친구2".to_string(), age: Some(20) }
);
assert_eq!(json, ret);
``` ```

View File

@ -3,6 +3,7 @@ extern crate jsonpath_lib as jsonpath;
extern crate serde; extern crate serde;
extern crate serde_json; extern crate serde_json;
extern crate test; extern crate test;
extern crate bencher;
use std::io::Read; use std::io::Read;
@ -10,6 +11,8 @@ use serde::Deserialize;
use serde_json::Value; use serde_json::Value;
use self::test::Bencher; use self::test::Bencher;
use jsonpath::ref_value::model::RefValue;
use serde::ser::Serialize;
fn read_json(path: &str) -> String { fn read_json(path: &str) -> String {
let mut f = std::fs::File::open(path).unwrap(); let mut f = std::fs::File::open(path).unwrap();
@ -102,3 +105,24 @@ fn bench_select_as(b: &mut Bencher) {
} }
}); });
} }
#[bench]
fn bench_serde_ser(b: &mut Bencher) {
let json = get_json();
b.iter(move || {
for _ in 1..100 {
let _: RefValue = json.serialize(jsonpath::ref_value::ser::Serializer).unwrap().into();
}
});
}
#[bench]
fn bench_serde_de(b: &mut Bencher) {
let json_string = get_string();
let json_str = json_string.as_str();
b.iter(move || for _ in 1..100 {
let _: RefValue = serde_json::from_str(json_str).unwrap();
});
}

View File

@ -37,6 +37,8 @@ __extra () {
printf "\n" printf "\n"
sleep 1 sleep 1
cd "${DIR}"/javascript && echo "NodeJs - jsonpath-wasm - compile-alloc: " && time ./bench.sh wasmCompileAlloc ${ITER} cd "${DIR}"/javascript && echo "NodeJs - jsonpath-wasm - compile-alloc: " && time ./bench.sh wasmCompileAlloc ${ITER}
sleep 1
cd "${DIR}"/javascript && echo "NodeJs - jsonpath-wasm - Selector: " && time ./bench.sh wasmSelectorClass ${ITER}
} }
if [ "$1" = "extra" ]; then if [ "$1" = "extra" ]; then

View File

@ -86,7 +86,7 @@ function wasmCompile() {
} }
function wasmCompileAlloc() { function wasmCompileAlloc() {
let ptr = jpw.alloc_json(getJson()); let ptr = jpw.allocJson(getJson());
if (ptr == 0) { if (ptr == 0) {
console.error('Invalid pointer'); console.error('Invalid pointer');
return; return;
@ -98,7 +98,7 @@ function wasmCompileAlloc() {
let _ = template(ptr); let _ = template(ptr);
} }
} finally { } finally {
jpw.dealloc_json(ptr); jpw.deallocJson(ptr);
} }
} }
@ -109,7 +109,7 @@ function wasmSelect() {
} }
function wasmSelectAlloc() { function wasmSelectAlloc() {
let ptr = jpw.alloc_json(getJson()); let ptr = jpw.allocJson(getJson());
if (ptr == 0) { if (ptr == 0) {
console.error('Invalid pointer'); console.error('Invalid pointer');
return; return;
@ -120,7 +120,16 @@ function wasmSelectAlloc() {
let _ = jpw.select(ptr, path); let _ = jpw.select(ptr, path);
} }
} finally { } finally {
jpw.dealloc_json(ptr); jpw.deallocJson(ptr);
}
}
function wasmSelectorClass() {
let selector = new jpw.Selector();
for (var i = 0; i < iter; i++) {
selector.path(path);
selector.value(jsonStr);
let _ = selector.selectToStr();
} }
} }

View File

@ -54,11 +54,11 @@ echo
echo echo
__msg "wasm-pack" __msg "wasm-pack"
cd "${WASM}" && \ cd "${WASM}" && \
wasm-pack build --target=nodejs --scope nodejs --out-dir nodejs_pkg && \ wasm-pack build --release --target=nodejs --scope nodejs --out-dir nodejs_pkg && \
cd "${WASM_NODEJS_PKG}" && npm link cd "${WASM_NODEJS_PKG}" && npm link
cd "${WASM}" && \ cd "${WASM}" && \
wasm-pack build --target=browser --scope browser --out-dir browser_pkg && \ wasm-pack build --release --target=browser --scope browser --out-dir browser_pkg && \
cd "${WASM_BROWSER_PKG}" && npm link cd "${WASM_BROWSER_PKG}" && npm link
echo echo

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -88,11 +88,14 @@
/******/ "__wbindgen_throw": function(p0i32,p1i32) { /******/ "__wbindgen_throw": function(p0i32,p1i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_throw"](p0i32,p1i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_throw"](p0i32,p1i32);
/******/ }, /******/ },
/******/ "__wbindgen_closure_wrapper102": function(p0i32,p1i32,p2i32) { /******/ "__wbindgen_rethrow": function(p0i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper102"](p0i32,p1i32,p2i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_rethrow"](p0i32);
/******/ }, /******/ },
/******/ "__wbindgen_closure_wrapper104": function(p0i32,p1i32,p2i32) { /******/ "__wbindgen_closure_wrapper103": function(p0i32,p1i32,p2i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper104"](p0i32,p1i32,p2i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper103"](p0i32,p1i32,p2i32);
/******/ },
/******/ "__wbindgen_closure_wrapper105": function(p0i32,p1i32,p2i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper105"](p0i32,p1i32,p2i32);
/******/ } /******/ }
/******/ } /******/ }
/******/ }; /******/ };
@ -192,7 +195,7 @@
/******/ promises.push(installedWasmModuleData); /******/ promises.push(installedWasmModuleData);
/******/ else { /******/ else {
/******/ var importObject = wasmImportObjects[wasmModuleId](); /******/ var importObject = wasmImportObjects[wasmModuleId]();
/******/ var req = fetch(__webpack_require__.p + "" + {"../browser_pkg/jsonpath_wasm_bg.wasm":"c615fa3fad4c084c8bcd"}[wasmModuleId] + ".module.wasm"); /******/ var req = fetch(__webpack_require__.p + "" + {"../browser_pkg/jsonpath_wasm_bg.wasm":"68fa958468b8cdcb12e4"}[wasmModuleId] + ".module.wasm");
/******/ var promise; /******/ var promise;
/******/ if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') { /******/ if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') {
/******/ promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) { /******/ promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {

13
docs/bootstrap.js vendored
View File

@ -88,11 +88,14 @@
/******/ "__wbindgen_throw": function(p0i32,p1i32) { /******/ "__wbindgen_throw": function(p0i32,p1i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_throw"](p0i32,p1i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_throw"](p0i32,p1i32);
/******/ }, /******/ },
/******/ "__wbindgen_closure_wrapper102": function(p0i32,p1i32,p2i32) { /******/ "__wbindgen_rethrow": function(p0i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper102"](p0i32,p1i32,p2i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_rethrow"](p0i32);
/******/ }, /******/ },
/******/ "__wbindgen_closure_wrapper104": function(p0i32,p1i32,p2i32) { /******/ "__wbindgen_closure_wrapper103": function(p0i32,p1i32,p2i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper104"](p0i32,p1i32,p2i32); /******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper103"](p0i32,p1i32,p2i32);
/******/ },
/******/ "__wbindgen_closure_wrapper105": function(p0i32,p1i32,p2i32) {
/******/ return installedModules["../browser_pkg/jsonpath_wasm.js"].exports["__wbindgen_closure_wrapper105"](p0i32,p1i32,p2i32);
/******/ } /******/ }
/******/ } /******/ }
/******/ }; /******/ };
@ -192,7 +195,7 @@
/******/ promises.push(installedWasmModuleData); /******/ promises.push(installedWasmModuleData);
/******/ else { /******/ else {
/******/ var importObject = wasmImportObjects[wasmModuleId](); /******/ var importObject = wasmImportObjects[wasmModuleId]();
/******/ var req = fetch(__webpack_require__.p + "" + {"../browser_pkg/jsonpath_wasm_bg.wasm":"c615fa3fad4c084c8bcd"}[wasmModuleId] + ".module.wasm"); /******/ var req = fetch(__webpack_require__.p + "" + {"../browser_pkg/jsonpath_wasm_bg.wasm":"68fa958468b8cdcb12e4"}[wasmModuleId] + ".module.wasm");
/******/ var promise; /******/ var promise;
/******/ if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') { /******/ if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') {
/******/ promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) { /******/ promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {

View File

@ -4,6 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>JsonPath evaluator - Webassembly via Rust</title> <title>JsonPath evaluator - Webassembly via Rust</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<link href="./css/github-corner.css" rel="stylesheet" type="text/css">
</head> </head>
<body role="document"> <body role="document">
<div class="container"> <div class="container">
@ -15,7 +16,59 @@
</div> </div>
--> -->
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-3">
<span class="badge badge-dark" style="margin-bottom: 15px">JsonPath</span> <span>(click to try)</span>
<table class="table">
<tbody>
<tr>
<td class="path"><a href="#/$.store.book[*].author">$.store.book[*].author</a></td>
</tr>
<tr>
<td class="path"><a href="#$..author">$..author</a></td>
</tr>
<tr>
<td class="path"><a href="#">$.store.*</a></td>
</tr>
<tr>
<td class="path"><a href="#">$.store..price</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[2]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[-2]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[0,1]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[:2]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[1:2]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[-2:]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[2:]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[?(@.isbn)]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$.store.book[?(@.price < 10)]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..*</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[ ?( (@.price < 13 || $.store.bicycle.price < @.price) && @.price <=10 ) ]</a></td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-4">
<span class="badge badge-dark" style="margin-bottom: 15px">Evaluator</span> <span class="badge badge-dark" style="margin-bottom: 15px">Evaluator</span>
<div class="form-group"> <div class="form-group">
<textarea id="json-example" class="form-control" style="min-width: 100%" rows="20"></textarea> <textarea id="json-example" class="form-control" style="min-width: 100%" rows="20"></textarea>
@ -29,12 +82,19 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-4">
<span class="badge badge-dark" style="margin-bottom: 15px">Result</span> <span class="badge badge-dark" style="margin-bottom: 15px">Result</span>
<pre class="prettyprint result" id="read-result" style="background-color: transparent; border: none;"></pre> <pre class="prettyprint result" id="read-result" style="background-color: transparent; border: none;"></pre>
</div> </div>
</div> </div>
</div> </div>
<a href="https://github.com/freestrings/jsonpath" class="github-corner" aria-label="View source on GitHub">
<svg width="80" height="80" viewBox="0 0 250 250" style="position: absolute; top: 0px; right: 0px; border: 0px;" aria-hidden="true">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" fill="#151513"></path>
<path class="octo-arm" d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="#ffffff" style="transform-origin: 130px 106px;"></path>
<path class="octo-body" d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="#ffffff"></path>
</svg>
</a>
<script src="./bootstrap.js"></script> <script src="./bootstrap.js"></script>
</body> </body>
</html> </html>

View File

@ -12,14 +12,52 @@ Build from source instead of using pre-built binary, and if Rust is not installe
> Not yet tested in Windows > Not yet tested in Windows
## 목차 ## APIs
* [jsonpath.Selector](#jsonpathselector)
* [jsonpath.select(json: string|object, jsonpath: string)](#json-stringobject-jsonpath-string) * [jsonpath.select(json: string|object, jsonpath: string)](#json-stringobject-jsonpath-string)
* [jsonpath.compile(jsonpath: string)](#compilejsonpath-string) * [jsonpath.compile(jsonpath: string)](#compilejsonpath-string)
* [jsonpath.selector(json: string|object)](#selectorjson-stringobject) * [jsonpath.selector(json: string|object)](#selectorjson-stringobject)
* [Simple time check](https://github.com/freestrings/jsonpath/wiki/Simple-timecheck-jsonpath-native) * [Simple time check](https://github.com/freestrings/jsonpath/wiki/Simple-timecheck-jsonpath-native)
* [Other Examples](https://github.com/freestrings/jsonpath/wiki/Javascript-examples) * [Other Examples](https://github.com/freestrings/jsonpath/wiki/Javascript-examples)
### jsonpath.Selector
```javascript
let jsonObj = {
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]
};
let selector = new jsonpath.Selector().value(jsonObj);
{
let jsonObj = selector.path('$..[?(@.age >= 30)]').selectTo();
let resultObj = [{"name": "친구3", "age": 30}];
console.log(JSON.stringify(jsonObj) === JSON.stringify(resultObj));
}
{
let jsonObj = selector.path('$..[?(@.age == 20)]').selectTo();
let resultObj = [{"name": "친구1", "age": 20}, {"name": "친구2", "age": 20}];
console.log(JSON.stringify(jsonObj) === JSON.stringify(resultObj));
}
{
let jsonObj = selector.value({"friends": [ {"name": "친구5", "age": 20} ]}).selectTo();
let resultObj = [{"name": "친구5", "age": 20}];
console.log(JSON.stringify(jsonObj) === JSON.stringify(resultObj));
}
```
### jsonpath.select(json: string|object, jsonpath: string) ### jsonpath.select(json: string|object, jsonpath: string)
```javascript ```javascript

View File

@ -1,7 +1,7 @@
const { Compile, Selector, selectStr } = require('../native'); const { CompileFn, SelectorFn, selectStr, Selector: _Selector } = require('../native');
function compile(path) { function compile(path) {
let compile = new Compile(path); let compile = new CompileFn(path);
return (json) => { return (json) => {
if(typeof json != 'string') { if(typeof json != 'string') {
json = JSON.stringify(json) json = JSON.stringify(json)
@ -14,9 +14,9 @@ function selector(json) {
if(typeof json != 'string') { if(typeof json != 'string') {
json = JSON.stringify(json) json = JSON.stringify(json)
} }
let selector = new Selector(json); let selector = new SelectorFn(json);
return (path) => { return (path) => {
return JSON.parse(selector.selector(path)); return JSON.parse(selector.select(path));
} }
} }
@ -27,8 +27,38 @@ function select(json, path) {
return JSON.parse(selectStr(json, path)); return JSON.parse(selectStr(json, path));
} }
class Selector {
constructor() {
this._selector = new _Selector();
return this;
}
path(path) {
this._selector.path(path);
return this;
}
value(json) {
if(typeof json != 'string') {
json = JSON.stringify(json)
}
this._selector.value_from_str(json);
return this;
}
selectToStr() {
return this._selector.select_to_str();
}
selectTo() {
return JSON.parse(this.selectToStr());
}
}
module.exports = { module.exports = {
compile, compile,
selector, selector,
select select,
Selector
}; };

View File

@ -14,7 +14,7 @@ exclude = ["artifacts.json", "index.node"]
neon-build = "0.2.0" neon-build = "0.2.0"
[dependencies] [dependencies]
jsonpath_lib = "0.1.8" jsonpath_lib = "0.1.9"
neon = "0.2.0" neon = "0.2.0"
neon-serde = "0.1.1" neon-serde = "0.1.1"
serde_json = { version = "1.0", features = ["preserve_order"] } serde_json = { version = "1.0", features = ["preserve_order"] }

View File

@ -4,13 +4,13 @@ extern crate neon;
extern crate neon_serde; extern crate neon_serde;
extern crate serde_json; extern crate serde_json;
use std::ops::Deref;
use jsonpath::filter::value_filter::JsonValueFilter; use jsonpath::filter::value_filter::JsonValueFilter;
use jsonpath::parser::parser::{Node, NodeVisitor, Parser}; use jsonpath::parser::parser::{Node, NodeVisitor, Parser};
use jsonpath::ref_value::model::{RefValue, RefValueWrapper}; use jsonpath::ref_value::model::{RefValue, RefValueWrapper};
use jsonpath::Selector;
use neon::prelude::*; use neon::prelude::*;
use serde_json::Value; use serde_json::Value;
use std::ops::Deref;
/// ///
/// `neon_serde::from_value` has very poor performance. /// `neon_serde::from_value` has very poor performance.
@ -35,16 +35,20 @@ fn select_str(mut ctx: FunctionContext) -> JsResult<JsValue> {
} }
} }
pub struct Compile { pub struct CompileFn {
node: Node node: Node
} }
pub struct Selector { pub struct SelectorFn {
json: RefValueWrapper json: RefValueWrapper
} }
pub struct SelectorCls {
selector: Selector
}
declare_types! { declare_types! {
pub class JsCompile for Compile { pub class JsCompileFn for CompileFn {
init(mut ctx) { init(mut ctx) {
let path = ctx.argument::<JsString>(0)?.value(); let path = ctx.argument::<JsString>(0)?.value();
let mut parser = Parser::new(path.as_str()); let mut parser = Parser::new(path.as_str());
@ -54,7 +58,7 @@ declare_types! {
Err(e) => panic!("{:?}", e) Err(e) => panic!("{:?}", e)
}; };
Ok(Compile { node }) Ok(CompileFn { node })
} }
method template(mut ctx) { method template(mut ctx) {
@ -81,7 +85,7 @@ declare_types! {
} }
} }
pub class JsSelector for Selector { pub class JsSelectorFn for SelectorFn {
init(mut ctx) { init(mut ctx) {
let json_str = ctx.argument::<JsString>(0)?.value(); let json_str = ctx.argument::<JsString>(0)?.value();
let ref_value: RefValue = match serde_json::from_str(&json_str) { let ref_value: RefValue = match serde_json::from_str(&json_str) {
@ -89,10 +93,10 @@ declare_types! {
Err(e) => panic!("{:?}", e) Err(e) => panic!("{:?}", e)
}; };
Ok(Selector { json: ref_value.into() }) Ok(SelectorFn { json: ref_value.into() })
} }
method selector(mut ctx) { method select(mut ctx) {
let this = ctx.this(); let this = ctx.this();
let json = { let json = {
@ -117,9 +121,55 @@ declare_types! {
} }
} }
} }
pub class JsSelector for SelectorCls {
init(mut _ctx) {
Ok(SelectorCls { selector: Selector::new() })
}
method path(mut ctx) {
let mut this = ctx.this();
let path = ctx.argument::<JsString>(0)?.value();
{
let guard = ctx.lock();
let mut this = this.borrow_mut(&guard);
let _ = this.selector.path(&path);
}
Ok(JsUndefined::new().upcast())
}
method value_from_str(mut ctx) {
let mut this = ctx.this();
let json_str = ctx.argument::<JsString>(0)?.value();
{
let guard = ctx.lock();
let mut this = this.borrow_mut(&guard);
let _ = this.selector.value_from_str(&json_str);
}
Ok(JsUndefined::new().upcast())
}
method select_to_str(mut ctx) {
let mut this = ctx.this();
let result = {
let guard = ctx.lock();
let mut this = this.borrow_mut(&guard);
this.selector.select_to_str()
};
match result {
Ok(json_str) => Ok(JsString::new(&mut ctx, &json_str).upcast()),
Err(e) => panic!("{:?}", e)
}
}
}
} }
register_module!(mut m, { register_module!(mut m, {
m.export_class::<JsCompile>("Compile").expect("Compile class error"); m.export_class::<JsCompileFn>("CompileFn").expect("CompileFn class error");
m.export_class::<JsSelectorFn>("SelectorFn").expect("SelectorFn class error");
m.export_class::<JsSelector>("Selector").expect("Selector class error"); m.export_class::<JsSelector>("Selector").expect("Selector class error");
m.export_function("select", select)?; m.export_function("select", select)?;
m.export_function("selectStr", select_str)?; m.export_function("selectStr", select_str)?;

View File

@ -1,6 +1,6 @@
{ {
"name": "jsonpath-rs", "name": "jsonpath-rs",
"version": "0.1.5", "version": "0.1.7",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,11 +1,12 @@
{ {
"name": "jsonpath-rs", "name": "jsonpath-rs",
"version": "0.1.6", "version": "0.1.7",
"description": "It is JsonPath implementation. The core implementation is written in Rust", "description": "It is JsonPath implementation. The core implementation is written in Rust",
"author": "Changseok Han <freestrings@gmail.com>", "author": "Changseok Han <freestrings@gmail.com>",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [
"jsonpath", "jsonpath",
"rust-addon",
"rust-binding", "rust-binding",
"rust", "rust",
"rustlang", "rustlang",

View File

@ -1,5 +1,360 @@
const jsonpath = require('../lib/index.js'); const jsonpath = require('../lib/index.js');
let jsonObj = {
"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
}
},
"expensive": 10
};
let list = {
'$.store.book[*].author': [
"Nigel Rees",
"Evelyn Waugh",
"Herman Melville",
"J. R. R. Tolkien"
],
'$..author':[
"Nigel Rees",
"Evelyn Waugh",
"Herman Melville",
"J. R. R. Tolkien"
],
'$.store.*': [
[
{
"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
}
],
{
"color": "red",
"price": 19.95
}
],
'$.store..price':[
8.95,
12.99,
8.99,
22.99,
19.95
],
'$..book[2]': [
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
}
],
'$..book[-2]': [
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
}
],
'$..book[0,1]': [
{
"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
}
],
'$..book[:2]': [
{
"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
}
],
'$..book[1:2]': [
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
}
],
'$..book[-2:]': [
{
"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
}
],
'$..book[2:]': [
{
"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
}
],
'$..book[?(@.isbn)]': [
{
"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
}
],
'$.store.book[?(@.price < 10)]': [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
}
],
'$..*': [
{
"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
}
},
10,
[
{
"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
}
],
{
"color": "red",
"price": 19.95
},
{
"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
},
"reference",
"Nigel Rees",
"Sayings of the Century",
8.95,
"fiction",
"Evelyn Waugh",
"Sword of Honour",
12.99,
"fiction",
"Herman Melville",
"Moby Dick",
"0-553-21311-3",
8.99,
"fiction",
"J. R. R. Tolkien",
"The Lord of the Rings",
"0-395-19395-8",
22.99,
"red",
19.95
],
'$..book[ ?( (@.price < 13 || $.store.bicycle.price < @.price) && @.price <=10 ) ]': [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
}
]
};
describe('compile test', () => { describe('compile test', () => {
it('basic', (done) => { it('basic', (done) => {
let template = jsonpath.compile('$.a'); let template = jsonpath.compile('$.a');
@ -30,70 +385,218 @@ describe('select test', () => {
}); });
describe('filter test', () => { describe('filter test', () => {
it('complex filter1', (done) => {
let json = {
'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,
},
},
'expensive': 10,
};
let target = [ function run(done, path, expected) {
{ let result = jsonpath.select(jsonObj, path);
category: 'fiction', if (JSON.stringify(result) === JSON.stringify(expected)) {
author: 'Evelyn Waugh', done();
title: 'Sword of Honour', }
price: 12.99, }
},
{
category: 'fiction',
author: 'J. R. R. Tolkien',
title: 'The Lord of the Rings',
isbn: '0-395-19395-8',
price: 22.99,
},
{
category: 'reference',
author: 'Nigel Rees',
title: 'Sayings of the Century',
price: 8.95,
}]
;
let result = jsonpath.select(json, '$..book[?((@.price == 12.99 || $.store.bicycle.price < @.price) || @.category == "reference")]'); for( var i in list ) {
if (JSON.stringify(result) === JSON.stringify(target)) { it(i, (done) => {
run (done, i, list[i]);
})
}
});
describe('Selector test', () => {
it('basic selectTo', (done) => {
let result = new jsonpath.Selector().path('$.a').value({'a': 1}).selectTo();
if (result === 1) {
done(); done();
} }
}); });
it('basic selectToStr', (done) => {
let result = new jsonpath.Selector().path('$.a').value({'a': 1}).selectToStr();
if (result === '1') {
done();
}
});
it('select', (done) => {
let selector = new jsonpath.Selector().value(jsonObj);
for(var i in list) {
if(JSON.stringify(list[i]) !== selector.path(i).selectToStr()) {
throw `fail: ${i}`;
}
}
done();
});
});
describe('README test', () => {
it('jsonpath.Selector', (done) => {
let jsonObj = {
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]
};
let selector = new jsonpath.Selector().value(jsonObj);
{
let jsonObj = selector.path('$..[?(@.age >= 30)]').selectTo();
let resultObj = [{"name": "친구3", "age": 30}];
if(JSON.stringify(jsonObj) !== JSON.stringify(resultObj)) {
throw 'jsonpath.Selector: $..[?(@.age >= 30)]';
}
}
{
let jsonObj = selector.path('$..[?(@.age == 20)]').selectTo();
let resultObj = [{"name": "친구1", "age": 20}, {"name": "친구2", "age": 20}];
if(JSON.stringify(jsonObj) !== JSON.stringify(resultObj)) {
throw 'jsonpath.Selector: $..[?(@.age >= 20)]';
}
}
{
let jsonObj = selector.value({"friends": [ {"name": "친구5", "age": 20} ]}).selectTo();
let resultObj = [{"name": "친구5", "age": 20}];
if(JSON.stringify(jsonObj) !== JSON.stringify(resultObj)) {
throw 'jsonpath.Selector: change value';
}
}
done();
});
it('jsonpath.select(json: string|object, jsonpath: string)', (done) => {
let jsonObj = {
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]
};
let ret = [
{"name": "친구3", "age": 30},
{"name": "친구1", "age": 20}
];
let selectAsString = jsonpath.select(JSON.stringify(jsonObj), '$..friends[0]');
let selectAsObj = jsonpath.select(jsonObj, '$..friends[0]');
if(
JSON.stringify(ret) !== JSON.stringify(selectAsString) ||
JSON.stringify(ret) !== JSON.stringify(selectAsObj)
) {
throw 'jsonpath.select(json: string|object, jsonpath: string)';
}
done();
});
it('jsonpath.compile(jsonpath: string)', (done) => {
let template = jsonpath.compile('$..friends[0]');
let jsonObj = {
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]
};
let ret = [
{"name": "친구3", "age": 30},
{"name": "친구1", "age": 20}
];
let selectAsString = template(JSON.stringify(jsonObj));
let selectAsObj = template(jsonObj);
if(
JSON.stringify(ret) !== JSON.stringify(selectAsString) ||
JSON.stringify(ret) !== JSON.stringify(selectAsObj)
) {
throw 'jsonpath.compile(jsonpath: string) 1';
}
let jsonObj2 = {
"school": {
"friends": [
{"name": "Millicent Norman"},
{"name": "Vincent Cannon"}
]
},
"friends": [ {"age": 30}, {"age": 40} ]
};
let ret2 = [
{"age": 30},
{"name": "Millicent Norman"}
];
let selectAsString2 = template(JSON.stringify(jsonObj2));
let selectAsObj2 = template(jsonObj2);
if(
JSON.stringify(ret2) !== JSON.stringify(selectAsString2) ||
JSON.stringify(ret2) !== JSON.stringify(selectAsObj2)
) {
throw 'jsonpath.compile(jsonpath: string) 2';
}
done();
});
it('jsonpath.selector(json: string|object)', (done) => {
let jsonObj = {
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]
};
let ret1 = [
{"name": "친구3", "age": 30},
{"name": "친구1", "age": 20}
];
let ret2 = [
{"name": "친구4"},
{"name": "친구2", "age": 20}
];
let selector = jsonpath.selector(jsonObj);
let select1 = selector('$..friends[0]');
let select2 = selector('$..friends[1]');
if(
JSON.stringify(ret1) !== JSON.stringify(select1) ||
JSON.stringify(ret2) !== JSON.stringify(select2)
) {
throw 'jsonpath.selector(json: string|object)';
}
done();
});
}); });

View File

@ -6,8 +6,8 @@ use serde_json::Value;
use filter::term::*; use filter::term::*;
use filter::value_wrapper::*; use filter::value_wrapper::*;
use parser::parser::{FilterToken, NodeVisitor, ParseToken};
use ref_value::model::*; use ref_value::model::*;
use parser::parser::{ParseToken, FilterToken, NodeVisitor};
trait ArrayIndex { trait ArrayIndex {
fn index(&self, v: &RefValueWrapper) -> usize; fn index(&self, v: &RefValueWrapper) -> usize;
@ -56,14 +56,14 @@ pub enum ValueFilterKey {
#[derive(Debug)] #[derive(Debug)]
pub struct ValueFilter { pub struct ValueFilter {
vw: ValueWrapper, val_wrapper: ValueWrapper,
last_key: Option<ValueFilterKey>, last_key: Option<ValueFilterKey>,
filter_mode: bool, filter_mode: bool,
} }
impl ValueFilter { impl ValueFilter {
pub fn new(v: RefValueWrapper, is_leaves: bool, filter_mode: bool) -> Self { pub fn new(v: RefValueWrapper, is_leaves: bool, filter_mode: bool) -> Self {
ValueFilter { vw: ValueWrapper::new(v, is_leaves), last_key: None, filter_mode } ValueFilter { val_wrapper: ValueWrapper::new(v, is_leaves), last_key: None, filter_mode }
} }
fn iter_to_value_vec<'a, I: Iterator<Item=&'a RefValueWrapper>>(iter: I) -> Vec<RefValueWrapper> { fn iter_to_value_vec<'a, I: Iterator<Item=&'a RefValueWrapper>>(iter: I) -> Vec<RefValueWrapper> {
@ -130,11 +130,11 @@ impl ValueFilter {
pub fn step_leaves_all(&mut self) -> &ValueWrapper { pub fn step_leaves_all(&mut self) -> &ValueWrapper {
debug!("step_leaves_all"); debug!("step_leaves_all");
let mut buf = Vec::new(); let mut buf = Vec::new();
Self::collect_all(None, &self.vw.get_val(), &mut buf); Self::collect_all(None, &self.val_wrapper.get_val(), &mut buf);
trace!("step_leaves_all - {:?}", buf); trace!("step_leaves_all - {:?}", buf);
self.last_key = Some(ValueFilterKey::All); self.last_key = Some(ValueFilterKey::All);
self.vw = ValueWrapper::new(RefValue::Array(buf).into(), true); self.val_wrapper = ValueWrapper::new(RefValue::Array(buf).into(), true);
&self.vw &self.val_wrapper
} }
pub fn step_leaves_str(&mut self, key: &str) -> &ValueWrapper { pub fn step_leaves_str(&mut self, key: &str) -> &ValueWrapper {
@ -144,17 +144,17 @@ impl ValueFilter {
pub fn step_leaves_string(&mut self, key: &String) -> &ValueWrapper { pub fn step_leaves_string(&mut self, key: &String) -> &ValueWrapper {
debug!("step_leaves_string"); debug!("step_leaves_string");
let mut buf = Vec::new(); let mut buf = Vec::new();
Self::collect_all(Some(key), &self.vw.get_val(), &mut buf); Self::collect_all(Some(key), &self.val_wrapper.get_val(), &mut buf);
trace!("step_leaves_string - {:?}", buf); trace!("step_leaves_string - {:?}", buf);
self.last_key = Some(ValueFilterKey::String(key.clone())); self.last_key = Some(ValueFilterKey::String(key.clone()));
self.vw = ValueWrapper::new(RefValue::Array(buf).into(), true); self.val_wrapper = ValueWrapper::new(RefValue::Array(buf).into(), true);
&self.vw &self.val_wrapper
} }
pub fn step_in_all(&mut self) -> &ValueWrapper { pub fn step_in_all(&mut self) -> &ValueWrapper {
debug!("step_in_all"); debug!("step_in_all");
let vec = match self.vw.get_val().deref() { let vec = match self.val_wrapper.get_val().deref() {
RefValue::Object(ref map) => { RefValue::Object(ref map) => {
Self::iter_to_value_vec(map.values()) Self::iter_to_value_vec(map.values())
} }
@ -162,25 +162,25 @@ impl ValueFilter {
Self::iter_to_value_vec(list.iter()) Self::iter_to_value_vec(list.iter())
} }
RefValue::Null => Vec::new(), RefValue::Null => Vec::new(),
_ => vec![self.vw.get_val().clone()] _ => vec![self.val_wrapper.get_val().clone()]
}; };
self.last_key = Some(ValueFilterKey::All); self.last_key = Some(ValueFilterKey::All);
self.vw.replace(RefValue::Array(vec).into()); self.val_wrapper.replace(RefValue::Array(vec).into());
trace!("step_in_all - {:?}", self.vw.get_val()); trace!("step_in_all - {:?}", self.val_wrapper.get_val());
&self.vw &self.val_wrapper
} }
pub fn step_in_num(&mut self, key: &f64) -> &ValueWrapper { pub fn step_in_num(&mut self, key: &f64) -> &ValueWrapper {
debug!("step_in_num"); debug!("step_in_num");
trace!("step_in_num - before: leaves {}, filterMode {} - {:?}" trace!("step_in_num - before: leaves {}, filterMode {} - {:?}"
, self.vw.is_leaves() , self.val_wrapper.is_leaves()
, self.filter_mode , self.filter_mode
, self.vw.get_val()); , self.val_wrapper.get_val());
let v = if self.vw.is_leaves() { let v = if self.val_wrapper.is_leaves() {
let filter_mode = self.filter_mode; let filter_mode = self.filter_mode;
match self.vw.get_val().deref() { match self.val_wrapper.get_val().deref() {
RefValue::Array(ref vec) => { RefValue::Array(ref vec) => {
let mut ret = Vec::new(); let mut ret = Vec::new();
for v in vec { for v in vec {
@ -191,16 +191,16 @@ impl ValueFilter {
} }
RefValue::Array(ret).into() RefValue::Array(ret).into()
} }
_ => key.take_value(&self.vw.get_val()) _ => key.take_value(&self.val_wrapper.get_val())
} }
} else { } else {
key.take_value(&self.vw.get_val()) key.take_value(&self.val_wrapper.get_val())
}; };
self.last_key = Some(ValueFilterKey::Num(key.index(&v))); self.last_key = Some(ValueFilterKey::Num(key.index(&v)));
self.vw.replace(v); self.val_wrapper.replace(v);
trace!("step_in_num - after: {:?}", self.vw.get_val()); trace!("step_in_num - after: {:?}", self.val_wrapper.get_val());
&self.vw &self.val_wrapper
} }
pub fn step_in_str(&mut self, key: &str) -> &ValueWrapper { pub fn step_in_str(&mut self, key: &str) -> &ValueWrapper {
@ -210,13 +210,13 @@ impl ValueFilter {
pub fn step_in_string(&mut self, key: &String) -> &ValueWrapper { pub fn step_in_string(&mut self, key: &String) -> &ValueWrapper {
debug!("step_in_string"); debug!("step_in_string");
trace!("step_in_string - before: {},{},{:?}" trace!("step_in_string - before: {},{},{:?}"
, self.vw.is_leaves() , self.val_wrapper.is_leaves()
, self.filter_mode , self.filter_mode
, self.vw.get_val()); , self.val_wrapper.get_val());
let filter_mode = self.filter_mode; let filter_mode = self.filter_mode;
let is_leaves = self.vw.is_leaves(); let is_leaves = self.val_wrapper.is_leaves();
let val = match self.vw.get_val().deref() { let val = match self.val_wrapper.get_val().deref() {
RefValue::Array(ref vec) if is_leaves => { RefValue::Array(ref vec) if is_leaves => {
let mut buf = Vec::new(); let mut buf = Vec::new();
for mut v in vec { for mut v in vec {
@ -230,6 +230,11 @@ impl ValueFilter {
} }
} }
buf.append(&mut ret); buf.append(&mut ret);
} else if v.is_object() {
let nested_wrapper = Self::get_nested_object(v, key, filter_mode);
if !nested_wrapper.is_null() {
buf.push(nested_wrapper.clone());
}
} else { } else {
match v.get(key.clone()) { match v.get(key.clone()) {
Some(v) => buf.push(v.clone()), Some(v) => buf.push(v.clone()),
@ -251,7 +256,7 @@ impl ValueFilter {
RefValue::Array(ret).into() RefValue::Array(ret).into()
} }
_ => { _ => {
match self.vw.get_val().get(key.clone()) { match self.val_wrapper.get_val().get(key.clone()) {
Some(v) => v.clone(), Some(v) => v.clone(),
_ => RefValue::Null.into() _ => RefValue::Null.into()
} }
@ -259,12 +264,12 @@ impl ValueFilter {
}; };
self.last_key = Some(ValueFilterKey::String(key.clone())); self.last_key = Some(ValueFilterKey::String(key.clone()));
self.vw.replace(val); self.val_wrapper.replace(val);
trace!("step_in_string - after: {},{},{:?}" trace!("step_in_string - after: {},{},{:?}"
, self.vw.is_leaves() , self.val_wrapper.is_leaves()
, self.filter_mode , self.filter_mode
, self.vw.get_val()); , self.val_wrapper.get_val());
&self.vw &self.val_wrapper
} }
} }
@ -303,7 +308,7 @@ impl JsonValueFilter {
if from_current { if from_current {
self.filter_stack.last() self.filter_stack.last()
.map(|vf| { .map(|vf| {
ValueFilter::new(vf.vw.get_val().clone(), vf.vw.is_leaves(), from_current) ValueFilter::new(vf.val_wrapper.get_val().clone(), vf.val_wrapper.is_leaves(), from_current)
}) })
.and_then(|vf| { .and_then(|vf| {
Some(self.filter_stack.push(vf)) Some(self.filter_stack.push(vf))
@ -321,13 +326,13 @@ impl JsonValueFilter {
} else { } else {
match self.filter_stack.last_mut() { match self.filter_stack.last_mut() {
Some(vf) => { Some(vf) => {
vf.vw.set_leaves(is_leaves); vf.val_wrapper.set_leaves(is_leaves);
if v.is_null() { if v.is_null() {
vf.vw.replace(v); vf.val_wrapper.replace(v);
} else if v.is_array() && v.as_array().unwrap().is_empty() { } else if v.is_array() && v.as_array().unwrap().is_empty() {
vf.vw.replace(RefValue::Null.into()); vf.val_wrapper.replace(RefValue::Null.into());
} else if vf.vw.is_array() { } else if vf.val_wrapper.is_array() {
vf.vw.replace(v); vf.val_wrapper.replace(v);
} }
} }
_ => {} _ => {}
@ -337,14 +342,14 @@ impl JsonValueFilter {
pub fn into_value(&self) -> Value { pub fn into_value(&self) -> Value {
match self.filter_stack.last() { match self.filter_stack.last() {
Some(v) => v.vw.into_value(), Some(v) => v.val_wrapper.into_value(),
_ => Value::Null _ => Value::Null
} }
} }
pub fn take_value(&mut self) -> RefValueWrapper { pub fn take_value(&mut self) -> RefValueWrapper {
match self.filter_stack.last_mut() { match self.filter_stack.last_mut() {
Some(v) => v.vw.get_val().clone(), Some(v) => v.val_wrapper.get_val().clone(),
_ => RefValue::Null.into() _ => RefValue::Null.into()
} }
} }
@ -353,9 +358,9 @@ impl JsonValueFilter {
self.token_stack.pop(); self.token_stack.pop();
match self.filter_stack.last_mut() { match self.filter_stack.last_mut() {
Some(ref mut vf) if vf.vw.is_array() && vf.vw.is_leaves() => { Some(ref mut vf) if vf.val_wrapper.is_array() && vf.val_wrapper.is_leaves() => {
let mut ret = Vec::new(); let mut ret = Vec::new();
if let RefValue::Array(val) = vf.vw.get_val().deref() { if let RefValue::Array(val) = vf.val_wrapper.get_val().deref() {
for mut v in val { for mut v in val {
for i in &indices { for i in &indices {
let v = i.take_value(v); let v = i.take_value(v);
@ -365,17 +370,17 @@ impl JsonValueFilter {
} }
} }
} }
vf.vw.replace(RefValue::Array(ret).into()); vf.val_wrapper.replace(RefValue::Array(ret).into());
} }
Some(ref mut vf) if vf.vw.is_array() && !vf.vw.is_leaves() => { Some(ref mut vf) if vf.val_wrapper.is_array() && !vf.val_wrapper.is_leaves() => {
let mut ret = Vec::new(); let mut ret = Vec::new();
for i in indices { for i in indices {
let wrapper = i.take_value(&vf.vw.get_val()); let wrapper = i.take_value(&vf.val_wrapper.get_val());
if !wrapper.is_null() { if !wrapper.is_null() {
ret.push(wrapper.clone()); ret.push(wrapper.clone());
} }
} }
vf.vw.replace(RefValue::Array(ret).into()); vf.val_wrapper.replace(RefValue::Array(ret).into());
} }
_ => {} _ => {}
} }
@ -413,21 +418,21 @@ impl JsonValueFilter {
} }
match self.filter_stack.last_mut() { match self.filter_stack.last_mut() {
Some(ref mut vf) if vf.vw.is_array() && vf.vw.is_leaves() => { Some(ref mut vf) if vf.val_wrapper.is_array() && vf.val_wrapper.is_leaves() => {
let mut buf = Vec::new(); let mut buf = Vec::new();
if let RefValue::Array(vec) = vf.vw.get_val().deref() { if let RefValue::Array(vec) = vf.val_wrapper.get_val().deref() {
for mut v in vec { for mut v in vec {
let (from, to) = _from_to(from, to, v); let (from, to) = _from_to(from, to, v);
let mut v: Vec<RefValueWrapper> = _range(from, to, v); let mut v: Vec<RefValueWrapper> = _range(from, to, v);
buf.append(&mut v); buf.append(&mut v);
} }
} }
vf.vw.replace(RefValue::Array(buf).into()); vf.val_wrapper.replace(RefValue::Array(buf).into());
} }
Some(ref mut vf) if vf.vw.is_array() && !vf.vw.is_leaves() => { Some(ref mut vf) if vf.val_wrapper.is_array() && !vf.val_wrapper.is_leaves() => {
let (from, to) = _from_to(from, to, &vf.vw.get_val()); let (from, to) = _from_to(from, to, &vf.val_wrapper.get_val());
let vec: Vec<RefValueWrapper> = _range(from, to, vf.vw.get_val()); let vec: Vec<RefValueWrapper> = _range(from, to, vf.val_wrapper.get_val());
vf.vw.replace(RefValue::Array(vec).into()); vf.val_wrapper.replace(RefValue::Array(vec).into());
} }
_ => {} _ => {}
} }
@ -491,13 +496,13 @@ impl JsonValueFilter {
_ => { _ => {
match self.filter_stack.pop() { match self.filter_stack.pop() {
Some(mut vf) => { Some(mut vf) => {
let is_leaves = vf.vw.is_leaves(); let is_leaves = vf.val_wrapper.is_leaves();
match vf.vw.get_val().deref() { match vf.val_wrapper.get_val().deref() {
RefValue::Null | RefValue::Bool(false) => { RefValue::Null | RefValue::Bool(false) => {
self.replace_filter_stack(RefValue::Null.into(), is_leaves); self.replace_filter_stack(RefValue::Null.into(), is_leaves);
} }
_ => { _ => {
self.replace_filter_stack(vf.vw.get_val().clone(), is_leaves); self.replace_filter_stack(vf.val_wrapper.get_val().clone(), is_leaves);
} }
} }
} }
@ -548,8 +553,13 @@ impl NodeVisitor for JsonValueFilter {
self.push_value_filter(ParseToken::Relative == token); self.push_value_filter(ParseToken::Relative == token);
} }
ParseToken::In ParseToken::In
| ParseToken::Leaves | ParseToken::Leaves => {
| ParseToken::Array => { self.token_stack.push(token);
}
ParseToken::Array => {
if let Some(ParseToken::Leaves) = self.token_stack.last() {
self.token_all();
}
self.token_stack.push(token); self.token_stack.push(token);
} }
ParseToken::ArrayEof => { ParseToken::ArrayEof => {
@ -593,7 +603,7 @@ impl NodeVisitor for JsonValueFilter {
if self.token_stack.is_empty() && self.filter_stack.len() > 1 { if self.token_stack.is_empty() && self.filter_stack.len() > 1 {
match self.filter_stack.pop() { match self.filter_stack.pop() {
Some(vf) => { Some(vf) => {
self.term_stack.push(TermContext::Json(vf.last_key, vf.vw)); self.term_stack.push(TermContext::Json(vf.last_key, vf.val_wrapper));
} }
_ => {} _ => {}
} }

View File

@ -79,15 +79,15 @@ impl ValueWrapper {
match self.val.deref() { match self.val.deref() {
RefValue::Array(vec) => { RefValue::Array(vec) => {
let mut ret = Vec::new(); let mut set = IndexSet::new();
for v in vec { for v in vec {
if _filter_with_object(v, key, |vv| { if _filter_with_object(v, key, |vv| {
Self::cmp_with_term(vv, et, cmp, false, reverse) Self::cmp_with_term(vv, et, cmp, false, reverse)
}) { }) {
ret.push(v.clone()); set.insert(v.clone());
} }
} }
let ret = set.into_iter().collect();
Some(ValueWrapper::new(RefValue::Array(ret).into(), false)) Some(ValueWrapper::new(RefValue::Array(ret).into(), false))
} }
_ => None _ => None
@ -109,12 +109,13 @@ impl ValueWrapper {
_ => { _ => {
match &(*self.val) { match &(*self.val) {
RefValue::Array(vec) => { RefValue::Array(vec) => {
let mut ret = Vec::new(); let mut set = IndexSet::new();
for v in vec { for v in vec {
if Self::cmp_with_term(v, et, &cmp, false, reverse) { if Self::cmp_with_term(v, et, &cmp, false, reverse) {
ret.push(v.clone()); set.insert(v.clone());
} }
} }
let ret = set.into_iter().collect();
ValueWrapper::new(RefValue::Array(ret).into(), false) ValueWrapper::new(RefValue::Array(ret).into(), false)
} }
_ => { _ => {

View File

@ -158,6 +158,7 @@
//! assert_eq!(ret, json); //! assert_eq!(ret, json);
//! ``` //! ```
extern crate core;
extern crate env_logger; extern crate env_logger;
extern crate indexmap; extern crate indexmap;
#[macro_use] #[macro_use]
@ -166,40 +167,23 @@ extern crate log;
extern crate serde; extern crate serde;
extern crate serde_json; extern crate serde_json;
use std::error::Error; use core::borrow::BorrowMut;
use std::ops::Deref;
use std::result; use std::result;
use serde_json::Value; use serde_json::Value;
use filter::value_filter::JsonValueFilter;
use parser::parser::{NodeVisitor, Parser};
use ref_value::model::RefValueWrapper;
#[doc(hidden)] #[doc(hidden)]
pub mod parser; pub mod parser;
#[doc(hidden)] #[doc(hidden)]
pub mod filter; pub mod filter;
#[doc(hidden)] #[doc(hidden)]
pub mod ref_value; pub mod ref_value;
#[doc(hidden)]
pub mod select;
fn query_from_str(json: &str, path: &str) -> result::Result<JsonValueFilter, String> { pub use select::Selector;
let mut jf = JsonValueFilter::new(json)?;
let mut parser = Parser::new(path);
parser.parse(&mut jf)?;
Ok(jf)
}
fn query_from_json_wrapper(json_wrapper: RefValueWrapper, path: &str) -> result::Result<JsonValueFilter, String> { /// It is a high-order function. it compile a JsonPath and then returns a function. this return-function can be reused for different JsonObjects.
let mut jf = JsonValueFilter::new_from_value(json_wrapper);
let mut parser = Parser::new(path);
parser.parse(&mut jf)?;
Ok(jf)
}
/// It is a highorder function that compile a JsonPath then returns a function.
///
/// this return function can be reused for different JsonObjects.
/// ///
/// ```rust /// ```rust
/// extern crate jsonpath_lib as jsonpath; /// extern crate jsonpath_lib as jsonpath;
@ -227,23 +211,17 @@ fn query_from_json_wrapper(json_wrapper: RefValueWrapper, path: &str) -> result:
/// assert_eq!(json, ret); /// assert_eq!(json, ret);
/// ``` /// ```
pub fn compile<'a>(path: &'a str) -> impl FnMut(&Value) -> result::Result<Value, String> + 'a { pub fn compile<'a>(path: &'a str) -> impl FnMut(&Value) -> result::Result<Value, String> + 'a {
let mut parser = Parser::new(path); let mut selector = select::Selector::new();
let node = parser.compile(); let _ = selector.path(path);
let mut selector = Box::new(selector);
move |json| { move |json| {
match &node { let s: &mut select::Selector = selector.borrow_mut();
Ok(n) => { let _ = s.value(json.into());
let mut jf = JsonValueFilter::new_from_value(json.into()); s.select_to_value()
jf.visit(n.clone());
Ok((&jf.take_value()).into())
}
Err(e) => Err(e.clone())
}
} }
} }
/// It returns highorder function that return a function. /// It is a high-order function that return a function. this return-function has a jsonpath as argument and return a serde_json::value::Value. so you can use different JsonPath for one JsonObject.
///
/// this function has a jsonpath as argument and return a serde_json::value::Value. so you can use different JsonPath for one JsonObject.
/// ///
/// ```rust /// ```rust
/// extern crate jsonpath_lib as jsonpath; /// extern crate jsonpath_lib as jsonpath;
@ -277,17 +255,17 @@ pub fn compile<'a>(path: &'a str) -> impl FnMut(&Value) -> result::Result<Value,
/// ]); /// ]);
/// assert_eq!(json, ret); /// assert_eq!(json, ret);
/// ``` /// ```
pub fn selector(json: &Value) -> impl FnMut(&str) -> result::Result<Value, String> { pub fn selector<'a>(json: &Value) -> impl FnMut(&'a str) -> result::Result<Value, String> {
let wrapper: RefValueWrapper = json.into(); let mut selector = select::Selector::new();
move |path: &str| { let _ = selector.value(json.into());
let mut jf = query_from_json_wrapper(wrapper.clone(), path)?; let mut selector = Box::new(selector);
Ok((&jf.take_value()).into()) move |path: &'a str| {
let s: &mut select::Selector = selector.borrow_mut();
s.path(path)?.select_to_value()
} }
} }
/// It returns highorder function that returns a function. /// It is a high-order function that returns a function. this return-function has a jsonpath as argument and return a serde::Deserialize. so you can use different JsonPath for one JsonObject.
///
/// this function has a jsonpath as argument and return a serde::Deserialize. so you can use different JsonPath for one JsonObject.
/// ///
/// ```rust /// ```rust
/// extern crate jsonpath_lib as jsonpath; /// extern crate jsonpath_lib as jsonpath;
@ -331,19 +309,19 @@ pub fn selector(json: &Value) -> impl FnMut(&str) -> result::Result<Value, Strin
/// assert_eq!(json, ret); /// assert_eq!(json, ret);
/// ``` /// ```
pub fn selector_as<T: serde::de::DeserializeOwned>(json: &Value) -> impl FnMut(&str) -> result::Result<T, String> { pub fn selector_as<T: serde::de::DeserializeOwned>(json: &Value) -> impl FnMut(&str) -> result::Result<T, String> {
let wrapper: RefValueWrapper = json.into(); let mut selector = select::Selector::new();
let _ = selector.value(json.into());
move |path: &str| { move |path: &str| {
let mut jf = query_from_json_wrapper(wrapper.clone(), path)?; selector.path(path)?.select_to()
T::deserialize(jf.take_value().deref()).map_err(|e| format!("{:?}", e))
} }
} }
#[deprecated(since = "0.1.4", note = "Please use the selector function instead")] #[deprecated(since = "0.1.4", note = "Please use the selector function instead")]
pub fn reader(json: &Value) -> impl FnMut(&str) -> result::Result<Value, String> { pub fn reader<'a>(json: &Value) -> impl FnMut(&'a str) -> result::Result<Value, String> {
selector(json) selector(json)
} }
/// Select a JsonObject. it return a serde_json::value::Value. /// This function compile a jsonpath everytime and it convert `serde_json's Value` to `jsonpath's RefValue` everytime and then it return a `serde_json::value::Value`.
/// ///
/// ```rust /// ```rust
/// extern crate jsonpath_lib as jsonpath; /// extern crate jsonpath_lib as jsonpath;
@ -370,8 +348,8 @@ pub fn reader(json: &Value) -> impl FnMut(&str) -> result::Result<Value, String>
/// assert_eq!(json, ret); /// assert_eq!(json, ret);
/// ``` /// ```
pub fn select(json: &Value, path: &str) -> result::Result<Value, String> { pub fn select(json: &Value, path: &str) -> result::Result<Value, String> {
let mut jf = query_from_json_wrapper(json.into(), path)?; let mut selector = select::Selector::new();
Ok((&jf.take_value()).into()) selector.path(path)?.value(json.into())?.select_to_value()
} }
#[deprecated(since = "0.1.4", note = "Please use the select function instead")] #[deprecated(since = "0.1.4", note = "Please use the select function instead")]
@ -384,7 +362,7 @@ pub fn select_str(json: &str, path: &str) -> result::Result<String, String> {
select_as_str(json, path) select_as_str(json, path)
} }
/// Select a JsonObject. it return a JsonObject as String. /// This function compile a jsonpath everytime and it convert `&str` to `jsonpath's RefValue` everytime and then it return a json string.
/// ///
/// ```rust /// ```rust
/// extern crate jsonpath_lib as jsonpath; /// extern crate jsonpath_lib as jsonpath;
@ -408,11 +386,13 @@ pub fn select_str(json: &str, path: &str) -> result::Result<String, String> {
/// assert_eq!(ret, r#"[{"name":"친구3","age":30},{"name":"친구1","age":20}]"#); /// assert_eq!(ret, r#"[{"name":"친구3","age":30},{"name":"친구1","age":20}]"#);
/// ``` /// ```
pub fn select_as_str(json: &str, path: &str) -> result::Result<String, String> { pub fn select_as_str(json: &str, path: &str) -> result::Result<String, String> {
let mut jf = query_from_str(json, path)?; select::Selector::new()
serde_json::to_string(&jf.take_value().deref()).map_err(|e| e.description().to_string()) .path(path)?
.value_from_str(json)?
.select_to_str()
} }
/// Select a JsonObject. it return a deserialized instance of type `T` /// This function compile a jsonpath everytime and it convert `&str` to `jsonpath's RefValue` everytime and then it return a deserialized-instance of type `T`.
/// ///
/// ```rust /// ```rust
/// extern crate jsonpath_lib as jsonpath; /// extern crate jsonpath_lib as jsonpath;
@ -451,6 +431,8 @@ pub fn select_as_str(json: &str, path: &str) -> result::Result<String, String> {
/// assert_eq!(person, ret); /// assert_eq!(person, ret);
/// ``` /// ```
pub fn select_as<T: serde::de::DeserializeOwned>(json: &str, path: &str) -> result::Result<T, String> { pub fn select_as<T: serde::de::DeserializeOwned>(json: &str, path: &str) -> result::Result<T, String> {
let mut jf = query_from_str(json, path)?; select::Selector::new()
T::deserialize(jf.take_value().deref()).map_err(|e| e.description().to_string()) .path(path)?
.value_from_str(json)?
.select_to()
} }

View File

@ -245,6 +245,15 @@ impl Into<RefValueWrapper> for RefValue {
} }
} }
impl Into<RefValue> for &Value {
fn into(self) -> RefValue {
match self.serialize(super::ser::Serializer) {
Ok(v) => v,
Err(e) => panic!("Error Value into RefValue: {:?}", e)
}
}
}
impl Into<RefValueWrapper> for &Value { impl Into<RefValueWrapper> for &Value {
fn into(self) -> RefValueWrapper { fn into(self) -> RefValueWrapper {
match self.serialize(super::ser::Serializer) { match self.serialize(super::ser::Serializer) {

152
src/select/mod.rs Normal file
View File

@ -0,0 +1,152 @@
use std::ops::Deref;
use std::result;
use serde_json::Value;
use super::filter::value_filter::*;
use super::parser::parser::*;
use super::ref_value::model::*;
/// Utility. Functions like jsonpath::selector or jsonpath::compile are also implemented using this structure.
///
/// ```rust
/// extern crate jsonpath_lib as jsonpath;
/// extern crate serde;
/// extern crate serde_json;
///
/// use serde::{Deserialize, Serialize};
/// use serde_json::Value;
///
/// use jsonpath::Selector;
///
/// #[derive(Serialize, Deserialize, PartialEq, Debug)]
/// struct Person {
/// name: String,
/// age: u8,
/// phone: String,
/// }
///
/// fn input_str() -> &'static str {
/// r#"[
/// {
/// "name": "이름1",
/// "age": 40,
/// "phone": "+33 12341234"
/// },
/// {
/// "name": "이름2",
/// "age": 42,
/// "phone": "++44 12341234"
/// }
/// ]"#
/// }
///
/// fn input_json() -> Value {
/// serde_json::from_str(input_str()).unwrap()
/// }
///
/// fn input_person() -> Vec<Person> {
/// serde_json::from_str(input_str()).unwrap()
/// }
///
///
/// let mut selector = Selector::new();
///
/// let result = selector
/// .path("$..[?(@.age > 40)]").unwrap()
/// .value_from_str(input_str()).unwrap()
/// .select_to_value().unwrap();
/// assert_eq!(input_json()[1], result[0]);
///
/// let result = selector.select_to_str().unwrap();
/// assert_eq!(serde_json::to_string(&vec![&input_json()[1].clone()]).unwrap(), result);
///
/// let result = selector.select_to::<Vec<Person>>().unwrap();
/// assert_eq!(input_person()[1], result[0]);
///
/// let _ = selector.path("$..[?(@.age == 40)]");
///
/// let result = selector.select_to_value().unwrap();
/// assert_eq!(input_json()[0], result[0]);
///
/// let result = selector.select_to_str().unwrap();
/// assert_eq!(serde_json::to_string(&vec![&input_json()[0].clone()]).unwrap(), result);
///
/// let result = selector.select_to::<Vec<Person>>().unwrap();
/// assert_eq!(input_person()[0], result[0]);
/// ```
pub struct Selector {
pub(crate) node: Option<Node>,
pub(crate) value: Option<RefValueWrapper>,
}
impl Selector {
pub fn new() -> Self {
Selector { node: None, value: None }
}
pub fn path(&mut self, path: &str) -> result::Result<&mut Self, String> {
let mut parser = Parser::new(path);
self.node = Some(parser.compile()?);
Ok(self)
}
pub fn value(&mut self, ref_value: RefValue) -> result::Result<&mut Self, String> {
self.value = Some(ref_value.into());
Ok(self)
}
pub fn value_from(&mut self, serializable: &impl serde::ser::Serialize) -> result::Result<&mut Self, String> {
let ref_value: RefValue = serializable
.serialize(super::ref_value::ser::Serializer)
.map_err(|e| format!("{:?}", e))?;
self.value(ref_value)
}
pub fn value_from_str(&mut self, json_str: &str) -> result::Result<&mut Self, String> {
let ref_value: RefValue = serde_json::from_str(json_str)
.map_err(|e| format!("{:?}", e))?;
self.value(ref_value)
}
fn jf(&mut self) -> result::Result<JsonValueFilter, String> {
match &self.value {
Some(v) => Ok(JsonValueFilter::new_from_value(v.clone())),
_ => return Err("Value is empty".to_owned())
}
}
pub fn select_to_str(&mut self) -> result::Result<String, String> {
let mut jf = self.jf()?;
match &mut self.node {
Some(node) => {
jf.visit(node.clone());
return serde_json::to_string(jf.take_value().deref()).map_err(|e| format!("{:?}", e));
}
_ => return Err("Path is empty".to_owned())
};
}
pub fn select_to_value(&mut self) -> result::Result<Value, String> {
let mut jf = self.jf()?;
match &mut self.node {
Some(node) => {
jf.visit(node.clone());
Ok((&jf.take_value()).into())
}
_ => Err("Path is empty".to_owned())
}
}
pub fn select_to<T: serde::de::DeserializeOwned>(&mut self) -> result::Result<T, String> {
let mut jf = self.jf()?;
match &mut self.node {
Some(node) => {
jf.visit(node.clone());
T::deserialize(jf.take_value().deref()).map_err(|e| format!("{:?}", e))
}
_ => Err("Path is empty".to_owned())
}
}
}

View File

@ -201,6 +201,36 @@ fn op_default() {
let jf = do_filter("$..book[?( (@.price == 12.99 || @.category == 'reference') && @.price > 10)].price", "./benches/example.json"); let jf = do_filter("$..book[?( (@.price == 12.99 || @.category == 'reference') && @.price > 10)].price", "./benches/example.json");
let friends = json!([12.99]); let friends = json!([12.99]);
assert_eq!(friends, jf.into_value()); assert_eq!(friends, jf.into_value());
let ref value = json!([
{ "name": "이름1", "age": 40, "phone": "+33 12341234" },
{ "name": "이름2", "age": 42, "phone": "++44 12341234" }
]);
let mut jf = JsonValueFilter::new_from_value(value.into());
let mut parser = Parser::new("$..[?(@.age > 40)]");
parser.parse(&mut jf).unwrap();
let friends = json!([
{ "name" : "이름2", "age" : 42, "phone" : "++44 12341234" }
]);
assert_eq!(friends, jf.into_value());
let ref value = json!({
"school": {
"friends": [
{"name": "친구1", "age": 20},
{"name": "친구2", "age": 20}
]
},
"friends": [
{"name": "친구3", "age": 30},
{"name": "친구4"}
]});
let mut jf = JsonValueFilter::new_from_value(value.into());
let mut parser = Parser::new("$..[?(@.age >= 30)]");
parser.parse(&mut jf).unwrap();
let friends = json!([{ "name" : "친구3", "age" : 30 }]);
assert_eq!(friends, jf.into_value());
} }
#[test] #[test]

92
tests/selector.rs Normal file
View File

@ -0,0 +1,92 @@
extern crate jsonpath_lib as jsonpath;
extern crate serde;
extern crate serde_json;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use jsonpath::Selector;
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Person {
name: String,
age: u8,
phone: String,
}
fn input_str() -> &'static str {
r#"[
{
"name": "이름1",
"age": 40,
"phone": "+33 12341234"
},
{
"name": "이름2",
"age": 42,
"phone": "++44 12341234"
}
]"#
}
fn input_json() -> Value {
serde_json::from_str(input_str()).unwrap()
}
fn input_person() -> Vec<Person> {
serde_json::from_str(input_str()).unwrap()
}
#[test]
fn selector_value_from() {
let result = Selector::new()
.path("$..[?(@.age > 40)]").unwrap()
.value_from(&input_person()).unwrap()
.select_to::<Vec<Person>>().unwrap();
assert_eq!(input_person()[1], result[0]);
}
#[test]
fn selector_value() {
let result = Selector::new()
.path("$..[?(@.age > 40)]").unwrap()
.value((&input_json()).into()).unwrap()
.select_to_value().unwrap();
assert_eq!(input_json()[1], result[0]);
}
#[test]
fn selector_value_from_str() {
let result = Selector::new()
.path("$..[?(@.age > 40)]").unwrap()
.value_from_str(input_str()).unwrap()
.select_to_value().unwrap();
assert_eq!(input_json()[1], result[0]);
}
#[test]
fn selector_select_to() {
let mut selector = Selector::new();
let result = selector
.path("$..[?(@.age > 40)]").unwrap()
.value_from_str(input_str()).unwrap()
.select_to_value().unwrap();
assert_eq!(input_json()[1], result[0]);
let result = selector.select_to_str().unwrap();
assert_eq!(serde_json::to_string(&vec![&input_json()[1].clone()]).unwrap(), result);
let result = selector.select_to::<Vec<Person>>().unwrap();
assert_eq!(input_person()[1], result[0]);
let _ = selector.path("$..[?(@.age == 40)]");
let result = selector.select_to_value().unwrap();
assert_eq!(input_json()[0], result[0]);
let result = selector.select_to_str().unwrap();
assert_eq!(serde_json::to_string(&vec![&input_json()[0].clone()]).unwrap(), result);
let result = selector.select_to::<Vec<Person>>().unwrap();
assert_eq!(input_person()[0], result[0]);
}

View File

@ -1,6 +1,6 @@
[package] [package]
name = "jsonpath-wasm" name = "jsonpath_wasm"
version = "0.1.6" version = "0.1.7"
authors = ["Changseok Han <freestrings@gmail.com>"] authors = ["Changseok Han <freestrings@gmail.com>"]
description = "JsonPath Webassembly version compiled by Rust - Demo: https://freestrings.github.io/jsonpath" description = "JsonPath Webassembly version compiled by Rust - Demo: https://freestrings.github.io/jsonpath"
keywords = ["library", "jsonpath", "json", "webassembly"] keywords = ["library", "jsonpath", "json", "webassembly"]
@ -26,6 +26,9 @@ web-sys = { version = "0.3", features = ['console'] }
[dev-dependencies] [dev-dependencies]
wasm-bindgen-test = "0.2" wasm-bindgen-test = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
js-sys = "0.3"
[profile.release] [profile.release]
opt-level = "s" opt-level = "s"

View File

@ -7,19 +7,19 @@ extern crate wasm_bindgen;
extern crate web_sys; extern crate web_sys;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Deref;
use std::result::Result; use std::result::Result;
use std::sync::Mutex; use std::sync::Mutex;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use serde_json::Value;
use wasm_bindgen::prelude::*;
use web_sys::console;
use jsonpath::filter::value_filter::JsonValueFilter; use jsonpath::filter::value_filter::JsonValueFilter;
use jsonpath::parser::parser::{Node, NodeVisitor, Parser}; use jsonpath::parser::parser::{Node, NodeVisitor, Parser};
use jsonpath::ref_value::model::{RefValue, RefValueWrapper}; use jsonpath::ref_value::model::{RefValue, RefValueWrapper};
use jsonpath::Selector as _Selector;
use wasm_bindgen::prelude::*;
use web_sys::console;
mod utils; use std::result;
cfg_if! { cfg_if! {
if #[cfg(feature = "wee_alloc")] { if #[cfg(feature = "wee_alloc")] {
@ -29,11 +29,21 @@ cfg_if! {
} }
} }
cfg_if! {
if #[cfg(feature = "console_error_panic_hook")] {
extern crate console_error_panic_hook;
pub use self::console_error_panic_hook::set_once as set_panic_hook;
} else {
#[inline]
pub fn set_panic_hook() {}
}
}
fn filter_ref_value(json: RefValueWrapper, node: Node) -> JsValue { fn filter_ref_value(json: RefValueWrapper, node: Node) -> JsValue {
let mut jf = JsonValueFilter::new_from_value(json); let mut jf = JsonValueFilter::new_from_value(json);
jf.visit(node); jf.visit(node);
let taken: Value = (&jf.take_value()).into(); let taken = &jf.take_value();
match JsValue::from_serde(&taken) { match JsValue::from_serde(taken.deref()) {
Ok(js_value) => js_value, Ok(js_value) => js_value,
Err(e) => JsValue::from_str(&format!("Json deserialize error: {:?}", e)) Err(e) => JsValue::from_str(&format!("Json deserialize error: {:?}", e))
} }
@ -77,8 +87,8 @@ lazy_static! {
static ref CACHE_JSON_IDX: Mutex<usize> = Mutex::new(0); static ref CACHE_JSON_IDX: Mutex<usize> = Mutex::new(0);
} }
#[wasm_bindgen] #[wasm_bindgen(js_name = allocJson)]
pub fn alloc_json(js_value: JsValue) -> usize { pub extern fn alloc_json(js_value: JsValue) -> usize {
match into_serde_json(&js_value) { match into_serde_json(&js_value) {
Ok(json) => { Ok(json) => {
let mut map = CACHE_JSON.lock().unwrap(); let mut map = CACHE_JSON.lock().unwrap();
@ -98,8 +108,8 @@ pub fn alloc_json(js_value: JsValue) -> usize {
} }
} }
#[wasm_bindgen] #[wasm_bindgen(js_name = deallocJson)]
pub fn dealloc_json(ptr: usize) -> bool { pub extern fn dealloc_json(ptr: usize) -> bool {
let mut map = CACHE_JSON.lock().unwrap(); let mut map = CACHE_JSON.lock().unwrap();
map.remove(&ptr).is_some() map.remove(&ptr).is_some()
} }
@ -120,14 +130,6 @@ pub fn compile(path: &str) -> JsValue {
ret ret
} }
///
/// deprecated. use selector
///
#[wasm_bindgen]
pub fn reader(js_value: JsValue) -> JsValue {
selector(js_value)
}
#[wasm_bindgen] #[wasm_bindgen]
pub fn selector(js_value: JsValue) -> JsValue { pub fn selector(js_value: JsValue) -> JsValue {
let json = match js_value.as_f64() { let json = match js_value.as_f64() {
@ -168,11 +170,47 @@ pub fn select(js_value: JsValue, path: &str) -> JsValue {
} }
/// ///
/// deprecated. use select /// `wasm_bindgen` 제약으로 builder-pattern을 구사 할 수 없다.
/// ///
#[wasm_bindgen] #[wasm_bindgen]
pub fn read(js_value: JsValue, path: &str) -> JsValue { pub struct Selector {
select(js_value, path) selector: _Selector
}
#[wasm_bindgen]
impl Selector {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Selector { selector: _Selector::new() }
}
#[wasm_bindgen(catch)]
pub fn path(&mut self, path: &str) -> result::Result<(), JsValue> {
let _ = self.selector.path(path)?;
Ok(())
}
#[wasm_bindgen(catch)]
pub fn value(&mut self, value: JsValue) -> result::Result<(), JsValue> {
let ref_value = into_serde_json(&value)?;
let _ = self.selector.value(ref_value)?;
Ok(())
}
#[wasm_bindgen(catch, js_name = selectToStr)]
pub fn select_to_str(&mut self) -> result::Result<JsValue, JsValue> {
let json_str = self.selector.select_to_str()?;
Ok(JsValue::from_str(&json_str))
}
#[wasm_bindgen(catch, js_name = selectTo)]
pub fn select_to(&mut self) -> result::Result<JsValue, JsValue> {
let ref_value = self.selector.select_to::<RefValue>()
.map_err(|e| JsValue::from_str(&e))?;
Ok(JsValue::from_serde(&ref_value)
.map_err(|e| JsValue::from_str(&format!("{:?}", e)))?)
}
} }
#[wasm_bindgen] #[wasm_bindgen]

View File

@ -1,11 +0,0 @@
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "console_error_panic_hook")] {
extern crate console_error_panic_hook;
pub use self::console_error_panic_hook::set_once as set_panic_hook;
} else {
#[inline]
pub fn set_panic_hook() {}
}
}

View File

@ -1,13 +1,118 @@
//! Test suite for the Web and headless browsers.
#![cfg(target_arch = "wasm32")] #![cfg(target_arch = "wasm32")]
extern crate core;
extern crate js_sys;
extern crate jsonpath_wasm as jsonpath;
#[macro_use]
extern crate serde_json;
extern crate wasm_bindgen;
extern crate wasm_bindgen_test; extern crate wasm_bindgen_test;
use serde_json::Value;
use wasm_bindgen::*;
use wasm_bindgen_test::*; use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser); wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test] fn json_str() -> &'static str {
fn pass() { r#"
assert_eq!(1 + 1, 2); {
"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
}
},
"expensive": 10
}
"#
}
fn target_json() -> Value {
json!([{
"category" : "fiction",
"author" : "Herman Melville",
"title" : "Moby Dick",
"isbn" : "0-553-21311-3",
"price" : 8.99
}])
}
#[wasm_bindgen_test]
fn select() {
let json: Value = jsonpath::select(JsValue::from_str(json_str()), "$..book[2]").into_serde().unwrap();
assert_eq!(json, target_json());
}
#[wasm_bindgen_test]
fn compile() {
let js_value = jsonpath::compile("$..book[2]");
assert_eq!(js_value.is_function(), true);
let cb: &js_sys::Function = JsCast::unchecked_ref(js_value.as_ref());
let cb_result: JsValue = cb.call1(&js_value, &JsValue::from_str(json_str())).unwrap();
let json: Value = cb_result.into_serde().unwrap();
assert_eq!(json, target_json());
}
#[wasm_bindgen_test]
fn selector() {
let js_value = jsonpath::selector(JsValue::from_str(json_str()));
assert_eq!(js_value.is_function(), true);
let cb: &js_sys::Function = JsCast::unchecked_ref(js_value.as_ref());
let cb_result: JsValue = cb.call1(&js_value, &JsValue::from_str("$..book[2]")).unwrap();
let json: Value = cb_result.into_serde().unwrap();
assert_eq!(json, target_json());
}
#[wasm_bindgen_test]
fn alloc_dealloc_json() {
let ptr = jsonpath::alloc_json(JsValue::from_str(json_str()));
assert_eq!(ptr > 0, true);
let json: Value = jsonpath::select(JsValue::from_f64(ptr as f64), "$..book[2]").into_serde().unwrap();
assert_eq!(json, target_json());
assert_eq!(jsonpath::dealloc_json(ptr), true);
let err = jsonpath::select(JsValue::from_f64(ptr as f64), "$..book[2]").as_string().unwrap();
assert_eq!(err, "Invalid pointer".to_string());
}
#[wasm_bindgen_test]
fn selector_struct() {
let mut selector = jsonpath::Selector::new();
selector.path("$..book[2]").unwrap();
selector.value(JsValue::from_str(json_str())).unwrap();
let json: Value = selector.select_to().unwrap().into_serde().unwrap();
assert_eq!(json, target_json());
} }

View File

@ -0,0 +1,170 @@
/**
* GitHub Corners, page css
* Author: Tim Holman
*/
* {
box-sizing: border-box;
}
html, body {
font-family: Helvetica, Arial, sans-serif;
background: #fff;
margin: 0px;
}
h1 {
font-size: 30px;
}
p {
font-size: 16px;
font-weight: 100;
line-height: 24px;
}
.wrapper {
padding-right: 20px;
padding-left: 20px;
padding-top: 50px;
max-width: 580px;
margin: auto;
}
/**
* Demo Code
*/
.version {
border: 2px solid #eee;
margin-top: 25px;
height: 200px;
display: -webkit-flex;
display: flex;
}
.version-section {
padding: 10px;
height: 100%;
}
.version-section.dark {
background: #151513;
}
.demo {
border-right: 2px solid #eee;
position: relative;
width: 200px;
}
.code {
vertical-align: top;
width: 336px;
}
.code textarea {
border: 2px solid #eee;
outline: 0px;
height: 100%;
width: 100%;
font-family: monospace;
font-size: 10px;
}
.github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
@keyframes octocat-wave {
0% {
transform: rotate(0deg);
}
20% {
transform: rotate(-25deg);
}
40% {
transform: rotate(10deg);
}
60% {
transform: rotate(-25deg);
}
80% {
transform: rotate(10deg);
}
100% {
transform: rotate(0deg);
}
}
@media (max-width: 500px) {
.github-corner:hover .octo-arm {
animation: none;
}
.github-corner .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
}
/**
* Footer
*/
footer {
width: 100%;
margin-top: 25px;
margin-bottom: 100px;
background: #F3F3F3;
height: 50px;
padding: 15px;
padding-left: 25px;
padding-right: 25px;
color: #2D2D2D;
font-size: 13px;
letter-spacing: 1px;
font-family: monospace;
}
footer a {
color: #2d2d2d;
}
footer span {
margin-left: 10px;
margin-right: 10px;
}
.twitter-share-button {
margin-bottom: -4px;
}
/**
* Media
*/
@media (max-width: 500px) {
footer {
text-align: center;
height: auto;
line-height: 30px;
}
footer span {
display: none;
}
footer a {
display: block;
}
.twitter-share-button {
margin-bottom: -8px;
}
}

View File

@ -4,6 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>JsonPath evaluator - Webassembly via Rust</title> <title>JsonPath evaluator - Webassembly via Rust</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<link href="./css/github-corner.css" rel="stylesheet" type="text/css">
</head> </head>
<body role="document"> <body role="document">
<div class="container"> <div class="container">
@ -15,7 +16,59 @@
</div> </div>
--> -->
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-3">
<span class="badge badge-dark" style="margin-bottom: 15px">JsonPath</span> <span>(click to try)</span>
<table class="table">
<tbody>
<tr>
<td class="path"><a href="#/$.store.book[*].author">$.store.book[*].author</a></td>
</tr>
<tr>
<td class="path"><a href="#$..author">$..author</a></td>
</tr>
<tr>
<td class="path"><a href="#">$.store.*</a></td>
</tr>
<tr>
<td class="path"><a href="#">$.store..price</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[2]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[-2]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[0,1]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[:2]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[1:2]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[-2:]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[2:]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[?(@.isbn)]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$.store.book[?(@.price < 10)]</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..*</a></td>
</tr>
<tr>
<td class="path"><a href="#">$..book[ ?( (@.price < 13 || $.store.bicycle.price < @.price) && @.price <=10 ) ]</a></td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-4">
<span class="badge badge-dark" style="margin-bottom: 15px">Evaluator</span> <span class="badge badge-dark" style="margin-bottom: 15px">Evaluator</span>
<div class="form-group"> <div class="form-group">
<textarea id="json-example" class="form-control" style="min-width: 100%" rows="20"></textarea> <textarea id="json-example" class="form-control" style="min-width: 100%" rows="20"></textarea>
@ -29,12 +82,19 @@
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-4">
<span class="badge badge-dark" style="margin-bottom: 15px">Result</span> <span class="badge badge-dark" style="margin-bottom: 15px">Result</span>
<pre class="prettyprint result" id="read-result" style="background-color: transparent; border: none;"></pre> <pre class="prettyprint result" id="read-result" style="background-color: transparent; border: none;"></pre>
</div> </div>
</div> </div>
</div> </div>
<a href="https://github.com/freestrings/jsonpath" class="github-corner" aria-label="View source on GitHub">
<svg width="80" height="80" viewBox="0 0 250 250" style="position: absolute; top: 0px; right: 0px; border: 0px;" aria-hidden="true">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z" fill="#151513"></path>
<path class="octo-arm" d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="#ffffff" style="transform-origin: 130px 106px;"></path>
<path class="octo-body" d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="#ffffff"></path>
</svg>
</a>
<script src="./bootstrap.js"></script> <script src="./bootstrap.js"></script>
</body> </body>
</html> </html>

View File

@ -16,6 +16,10 @@ function getReadResult() {
return document.querySelector('#read-result'); return document.querySelector('#read-result');
} }
function getLinks() {
return document.querySelectorAll('.path>a');
}
function initData(url) { function initData(url) {
return fetch(url) return fetch(url)
.then((res) => res.text()) .then((res) => res.text())
@ -35,8 +39,12 @@ function initEvent() {
read(); read();
} }
getLinks().forEach(function(anchor) {
anchor.href = "#" + encodeURIComponent(anchor.textContent);
});
function read() { function read() {
let ret = jsonpath.read(getTextarea().value, getJsonpathInput().value); let ret = jsonpath.select(getTextarea().value, getJsonpathInput().value);
if(typeof ret === 'string') { if(typeof ret === 'string') {
getReadResult().innerText = ret; getReadResult().innerText = ret;
} else { } else {
@ -46,22 +54,26 @@ function initEvent() {
} }
function readPathParam() { function readPathParam() {
let params = location.search.substr(1) if(location.href.indexOf('#') > -1) {
.split('&') readPath()
.map((item) => item.split('=')) }
.reduce((acc, param) => { }
acc[param[0]] = decodeURIComponent(param[1]);
return acc;
}, {});
if(params.path) { function forceClick(ctrl) {
getJsonpathInput().value = params.path; let doc = ctrl.ownerDocument;
let doc = getReadBtn().ownerDocument;
let event = doc.createEvent('MouseEvents'); let event = doc.createEvent('MouseEvents');
event.initEvent('click', true, true); event.initEvent('click', true, true);
event.synthetic = true; event.synthetic = true;
getReadBtn().dispatchEvent(event, true); ctrl.dispatchEvent(event, true);
}
} }
function readPath() {
let query = location.href.substring(location.href.indexOf('#') + 1);
let path = decodeURIComponent(query);
getJsonpathInput().value = path;
forceClick(getReadBtn());
}
window.onpopstate = readPath;
initData('data/example.json').then(initEvent).then(readPathParam); initData('data/example.json').then(initEvent).then(readPathParam);

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 250 250" fill="#151513" style="position: absolute; top: 0; left: 0">
<path fill="#fff" d="M250 0L135 115h-15l-12 27L0 250V0z"/>
<path class="octo-arm" d="M122 109c15-9 9-19 9-19-3-7-2-11-2-11 1-7-3-2-3-2-4 5-2 11-2 11 3 10-5 15-9 16" style="-webkit-transform-origin: 120px 144px; transform-origin: 120px 144px"/>
<path class="octo-body" d="M135 115s-4 2-5 0l-14-14c-3-2-6-3-8-3 8-11 15-24-2-41-5-5-10-7-16-7-1-2-3-7-12-11 0 0-5 3-7 16-4 2-8 5-12 9s-7 8-9 12c-14 4-17 9-17 9 4 8 9 11 11 11 0 6 2 11 7 16 16 16 30 10 41 2 0 3 1 7 5 11l12 11c1 2-1 6-1 6z"/>
</svg>

After

Width:  |  Height:  |  Size: 644 B

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 250 250" fill="#151513" style="position: absolute; top: 0; right: 0">
<path d="M0 0l115 115h15l12 27 108 108V0z" fill="#fff"/>
<path class="octo-arm" d="M128 109c-15-9-9-19-9-19 3-7 2-11 2-11-1-7 3-2 3-2 4 5 2 11 2 11-3 10 5 15 9 16" style="-webkit-transform-origin: 130px 106px; transform-origin: 130px 106px"/>
<path class="octo-body" d="M115 115s4 2 5 0l14-14c3-2 6-3 8-3-8-11-15-24 2-41 5-5 10-7 16-7 1-2 3-7 12-11 0 0 5 3 7 16 4 2 8 5 12 9s7 8 9 12c14 3 17 7 17 7-4 8-9 11-11 11 0 6-2 11-7 16-16 16-30 10-41 2 0 3-1 7-5 11l-12 11c-1 1 1 5 1 5z"/>
</svg>

After

Width:  |  Height:  |  Size: 641 B

View File

@ -61,7 +61,7 @@ let path = '$..book[?(@.price<30 && @.category=="fiction")]';
let template = jpw.compile(path); let template = jpw.compile(path);
let selector = jpw.selector(json); let selector = jpw.selector(json);
let ptr = jpw.alloc_json(json); let ptr = jpw.allocJson(json);
if(ptr == 0) console.error('invalid ptr'); if(ptr == 0) console.error('invalid ptr');
let iterCount = 2000; let iterCount = 2000;
@ -83,7 +83,7 @@ run('jsonpath', iterCount, function() { jp.query(json, path) })
return run('jsonpath-wasm- select-alloc', iterCount, function() { jpw.select(ptr, path) }); return run('jsonpath-wasm- select-alloc', iterCount, function() { jpw.select(ptr, path) });
}) })
.finally(function() { .finally(function() {
if(!jpw.dealloc_json(ptr)) { if(!jpw.deallocJson(ptr)) {
console.error('fail to dealloc'); console.error('fail to dealloc');
} }
}); });