diff --git a/nodejs/README.md b/nodejs/README.md index facda902..0933b9c0 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -12,14 +12,52 @@ Build from source instead of using pre-built binary, and if Rust is not installe > Not yet tested in Windows -## 목차 +## APIs +* [jsonpath.Selector](#jsonpathselector) * [jsonpath.select(json: string|object, jsonpath: string)](#json-stringobject-jsonpath-string) * [jsonpath.compile(jsonpath: string)](#compilejsonpath-string) * [jsonpath.selector(json: string|object)](#selectorjson-stringobject) * [Simple time check](https://github.com/freestrings/jsonpath/wiki/Simple-timecheck-jsonpath-native) * [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) ```javascript diff --git a/nodejs/lib/index.js b/nodejs/lib/index.js index 49b1dca1..315f1737 100644 --- a/nodejs/lib/index.js +++ b/nodejs/lib/index.js @@ -1,7 +1,7 @@ -const { Compile, Selector, selectStr } = require('../native'); +const { CompileFn, SelectorFn, selectStr, Selector: _Selector } = require('../native'); function compile(path) { - let compile = new Compile(path); + let compile = new CompileFn(path); return (json) => { if(typeof json != 'string') { json = JSON.stringify(json) @@ -14,9 +14,9 @@ function selector(json) { if(typeof json != 'string') { json = JSON.stringify(json) } - let selector = new Selector(json); + let selector = new SelectorFn(json); 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)); } +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 = { compile, selector, - select + select, + Selector }; \ No newline at end of file diff --git a/nodejs/native/Cargo.toml b/nodejs/native/Cargo.toml index 8af07c1b..e8571e78 100644 --- a/nodejs/native/Cargo.toml +++ b/nodejs/native/Cargo.toml @@ -14,7 +14,7 @@ exclude = ["artifacts.json", "index.node"] neon-build = "0.2.0" [dependencies] -jsonpath_lib = "0.1.8" +jsonpath_lib = "0.1.9" neon = "0.2.0" neon-serde = "0.1.1" serde_json = { version = "1.0", features = ["preserve_order"] } diff --git a/nodejs/native/src/lib.rs b/nodejs/native/src/lib.rs index ede50b42..c24ae13e 100644 --- a/nodejs/native/src/lib.rs +++ b/nodejs/native/src/lib.rs @@ -4,13 +4,13 @@ extern crate neon; extern crate neon_serde; extern crate serde_json; -use std::ops::Deref; - use jsonpath::filter::value_filter::JsonValueFilter; use jsonpath::parser::parser::{Node, NodeVisitor, Parser}; use jsonpath::ref_value::model::{RefValue, RefValueWrapper}; +use jsonpath::Selector; use neon::prelude::*; use serde_json::Value; +use std::ops::Deref; /// /// `neon_serde::from_value` has very poor performance. @@ -35,16 +35,20 @@ fn select_str(mut ctx: FunctionContext) -> JsResult { } } -pub struct Compile { +pub struct CompileFn { node: Node } -pub struct Selector { +pub struct SelectorFn { json: RefValueWrapper } +pub struct SelectorCls { + selector: Selector +} + declare_types! { - pub class JsCompile for Compile { + pub class JsCompileFn for CompileFn { init(mut ctx) { let path = ctx.argument::(0)?.value(); let mut parser = Parser::new(path.as_str()); @@ -54,7 +58,7 @@ declare_types! { Err(e) => panic!("{:?}", e) }; - Ok(Compile { node }) + Ok(CompileFn { node }) } method template(mut ctx) { @@ -81,7 +85,7 @@ declare_types! { } } - pub class JsSelector for Selector { + pub class JsSelectorFn for SelectorFn { init(mut ctx) { let json_str = ctx.argument::(0)?.value(); let ref_value: RefValue = match serde_json::from_str(&json_str) { @@ -89,10 +93,10 @@ declare_types! { 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 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::(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::(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, { - m.export_class::("Compile").expect("Compile class error"); + m.export_class::("CompileFn").expect("CompileFn class error"); + m.export_class::("SelectorFn").expect("SelectorFn class error"); m.export_class::("Selector").expect("Selector class error"); m.export_function("select", select)?; m.export_function("selectStr", select_str)?; diff --git a/nodejs/package.json b/nodejs/package.json index 61a85054..c6e2f4ee 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -1,11 +1,12 @@ { "name": "jsonpath-rs", - "version": "0.1.6", + "version": "0.1.7", "description": "It is JsonPath implementation. The core implementation is written in Rust", "author": "Changseok Han ", "license": "MIT", "keywords": [ "jsonpath", + "rust-addon", "rust-binding", "rust", "rustlang", diff --git a/nodejs/test/index.spec.js b/nodejs/test/index.spec.js index 08b026fc..777f9558 100644 --- a/nodejs/test/index.spec.js +++ b/nodejs/test/index.spec.js @@ -1,5 +1,360 @@ 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', () => { it('basic', (done) => { let template = jsonpath.compile('$.a'); @@ -30,70 +385,218 @@ describe('select 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, - }, + + function run(done, path, expected) { + let result = jsonpath.select(jsonObj, path); + if (JSON.stringify(result) === JSON.stringify(expected)) { + done(); + } + } + + for( var i in list ) { + 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(); + } + }); + + 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} + ] }, - 'expensive': 10, + "friends": [ + {"name": "친구3", "age": 30}, + {"name": "친구4"} + ] }; - let target = [ - { - category: 'fiction', - author: 'Evelyn Waugh', - title: 'Sword of Honour', - price: 12.99, + 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} + ] }, - { - category: 'fiction', - author: 'J. R. R. Tolkien', - title: 'The Lord of the Rings', - isbn: '0-395-19395-8', - price: 22.99, + "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} + ] }, - { - 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")]'); - if (JSON.stringify(result) === JSON.stringify(target)) { - done(); + "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(); }); }); \ No newline at end of file