diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e70a5b..09975ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ ## [Unreleased] +## [0.0.0+dev.2] - 2020-07-28 +### Added +- Recursive selector +- Wildcard selector + ## [0.0.0+dev.1] - 2020-07-27 ### Added - Tokenizer and AST @@ -8,5 +13,6 @@ ### Added - Basic design draft -[Unreleased]: https://github.com/f3ath/jessie/compare/0.0.0+dev.1...HEAD +[Unreleased]: https://github.com/f3ath/jessie/compare/0.0.0+dev.2...HEAD +[0.0.0+dev.2]: https://github.com/f3ath/jessie/compare/0.0.0+dev.1...0.0.0+dev.2 [0.0.0+dev.1]: https://github.com/f3ath/jessie/compare/0.0.0+dev.0...0.0.0+dev.1 \ No newline at end of file diff --git a/README.md b/README.md index a7105d1..758a9ee 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # [JSONPath] for Dart -Jessie is a work-in-progress. Expect the API to change often. Feel free to join. +**Warning!** This is a work-in-progress. Expect the API to change often. Also, feel free to join. ## Roadmap - [x] Basic selectors: fields, indices diff --git a/example/main.dart b/example/main.dart new file mode 100644 index 0000000..74d9210 --- /dev/null +++ b/example/main.dart @@ -0,0 +1,56 @@ +import 'dart:convert'; + +import 'package:json_path/json_path.dart'; + +void main() { + final json = jsonDecode(''' +{ + "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 + } + } +} + '''); + + /// The following code will print: + /// + /// $['store']['book'][0]['price']: 8.95 + /// $['store']['book'][1]['price']: 12.99 + /// $['store']['book'][2]['price']: 8.99 + /// $['store']['book'][3]['price']: 22.99 + /// $['store']['bicycle']['price']: 19.95 + JsonPath(r'$..price') + .select(json) + .map((result) => '${result.path}:\t${result.value}') + .forEach(print); +} diff --git a/lib/jessie.dart b/lib/jessie.dart deleted file mode 100644 index 3799019..0000000 --- a/lib/jessie.dart +++ /dev/null @@ -1,5 +0,0 @@ -/// JSONPath for Dart -library jessie; - -export 'package:jessie/src/json_path.dart'; -export 'package:jessie/src/result.dart'; diff --git a/lib/json_path.dart b/lib/json_path.dart new file mode 100644 index 0000000..544ed13 --- /dev/null +++ b/lib/json_path.dart @@ -0,0 +1,5 @@ +/// JSONPath for Dart +library json_path; + +export 'package:json_path/src/json_path.dart'; +export 'package:json_path/src/result.dart'; diff --git a/lib/src/json_path.dart b/lib/src/json_path.dart index bbdaaea..3196b3b 100644 --- a/lib/src/json_path.dart +++ b/lib/src/json_path.dart @@ -1,9 +1,9 @@ -import 'package:jessie/src/ast.dart'; -import 'package:jessie/src/selector/selector.dart'; -import 'package:jessie/src/selector/root.dart'; -import 'package:jessie/src/result.dart'; -import 'package:jessie/src/state.dart'; -import 'package:jessie/src/tokenize.dart'; +import 'package:json_path/src/ast.dart'; +import 'package:json_path/src/result.dart'; +import 'package:json_path/src/selector/root.dart'; +import 'package:json_path/src/selector/selector.dart'; +import 'package:json_path/src/state.dart'; +import 'package:json_path/src/tokenize.dart'; class JsonPath { factory JsonPath(String expression) { diff --git a/lib/src/selector/all_in_array.dart b/lib/src/selector/all_in_array.dart index e11ec78..1f2e650 100644 --- a/lib/src/selector/all_in_array.dart +++ b/lib/src/selector/all_in_array.dart @@ -1,5 +1,5 @@ -import 'package:jessie/src/selector/selector.dart'; -import 'package:jessie/src/result.dart'; +import 'package:json_path/src/result.dart'; +import 'package:json_path/src/selector/selector.dart'; class AllInArray extends Selector { @override diff --git a/lib/src/selector/all_values.dart b/lib/src/selector/all_values.dart index b99e771..f33ac24 100644 --- a/lib/src/selector/all_values.dart +++ b/lib/src/selector/all_values.dart @@ -1,6 +1,6 @@ -import 'package:jessie/src/quote.dart'; -import 'package:jessie/src/result.dart'; -import 'package:jessie/src/selector/selector.dart'; +import 'package:json_path/src/quote.dart'; +import 'package:json_path/src/result.dart'; +import 'package:json_path/src/selector/selector.dart'; class AllValues extends Selector { @override diff --git a/lib/src/selector/field.dart b/lib/src/selector/field.dart index 2ee0df4..c19e34e 100644 --- a/lib/src/selector/field.dart +++ b/lib/src/selector/field.dart @@ -1,6 +1,6 @@ -import 'package:jessie/src/quote.dart'; -import 'package:jessie/src/result.dart'; -import 'package:jessie/src/selector/selector.dart'; +import 'package:json_path/src/quote.dart'; +import 'package:json_path/src/result.dart'; +import 'package:json_path/src/selector/selector.dart'; class Field extends Selector { Field(this.name); diff --git a/lib/src/selector/index.dart b/lib/src/selector/index.dart index 1b62a73..4171f17 100644 --- a/lib/src/selector/index.dart +++ b/lib/src/selector/index.dart @@ -1,5 +1,5 @@ -import 'package:jessie/src/selector/selector.dart'; -import 'package:jessie/src/result.dart'; +import 'package:json_path/src/result.dart'; +import 'package:json_path/src/selector/selector.dart'; class Index extends Selector { Index(this.index); diff --git a/lib/src/selector/recursive.dart b/lib/src/selector/recursive.dart index 340d52e..cd90e4a 100644 --- a/lib/src/selector/recursive.dart +++ b/lib/src/selector/recursive.dart @@ -1,6 +1,6 @@ -import 'package:jessie/src/quote.dart'; -import 'package:jessie/src/result.dart'; -import 'package:jessie/src/selector/selector.dart'; +import 'package:json_path/src/quote.dart'; +import 'package:json_path/src/result.dart'; +import 'package:json_path/src/selector/selector.dart'; class Recursive extends Selector { @override diff --git a/lib/src/selector/root.dart b/lib/src/selector/root.dart index 0c3b45a..b509e26 100644 --- a/lib/src/selector/root.dart +++ b/lib/src/selector/root.dart @@ -1,5 +1,5 @@ -import 'package:jessie/src/selector/selector.dart'; -import 'package:jessie/src/result.dart'; +import 'package:json_path/src/result.dart'; +import 'package:json_path/src/selector/selector.dart'; class Root extends Selector { const Root(); diff --git a/lib/src/selector/selector.dart b/lib/src/selector/selector.dart index c4b712a..7d7f9d5 100644 --- a/lib/src/selector/selector.dart +++ b/lib/src/selector/selector.dart @@ -1,6 +1,6 @@ -import 'package:jessie/src/result.dart'; -import 'package:jessie/src/selector/all_values.dart'; -import 'package:jessie/src/selector/recursive.dart'; +import 'package:json_path/src/result.dart'; +import 'package:json_path/src/selector/all_values.dart'; +import 'package:json_path/src/selector/recursive.dart'; /// Converts a set of results into a set of results abstract class Selector { diff --git a/lib/src/state.dart b/lib/src/state.dart index b2ec251..08d1a72 100644 --- a/lib/src/state.dart +++ b/lib/src/state.dart @@ -1,10 +1,10 @@ -import 'package:jessie/src/ast.dart'; -import 'package:jessie/src/selector/all_in_array.dart'; -import 'package:jessie/src/selector/all_values.dart'; -import 'package:jessie/src/selector/field.dart'; -import 'package:jessie/src/selector/index.dart'; -import 'package:jessie/src/selector/recursive.dart'; -import 'package:jessie/src/selector/selector.dart'; +import 'package:json_path/src/ast.dart'; +import 'package:json_path/src/selector/all_in_array.dart'; +import 'package:json_path/src/selector/all_values.dart'; +import 'package:json_path/src/selector/field.dart'; +import 'package:json_path/src/selector/index.dart'; +import 'package:json_path/src/selector/recursive.dart'; +import 'package:json_path/src/selector/selector.dart'; /// AST parser state abstract class State { @@ -24,28 +24,24 @@ class Ready implements State { @override State process(Node node) { - if (node.value == '[') { - return Ready(selector.then(_brackets(node.children))); + switch (node.value) { + case '[': + return Ready(selector.then(_bracketExpression(node.children))); + case '.': + return AwaitingField(selector); + case '..': + return Ready(selector.then(Recursive())); + case '*': + return Ready(selector.then(AllValues())); + default: + return Ready(selector.then(Field(node.value))); } - if (node.value == '.') { - return AwaitingField(selector); - } - if (node.value == '..') { - return Ready(selector.then(Recursive())); - } - if (node.value == '*') { - return Ready(selector.then(AllValues())); - } - - throw StateError('Got ${node.value} in $this'); } - Selector _brackets(List nodes) { - if (nodes.length == 1) { - final node = nodes.single; - if (node.value == '*') return AllInArray(); - if (node.isNumber) return Index(int.parse(nodes.first.value)); - } + Selector _bracketExpression(List nodes) { + final node = nodes.single; + if (node.value == '*') return AllInArray(); + if (node.isNumber) return Index(int.parse(nodes.first.value)); throw StateError('Unexpected bracket expression'); } } diff --git a/pubspec.yaml b/pubspec.yaml index 5890edf..98f4ec5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ -name: jessie -version: 0.0.0+dev.1 -description: JSONPath for Dart +name: json_path +version: 0.0.0+dev.2 +description: JSONPath for Dart. JSONPath is XPath for JSON. It is a path in a JSON document. homepage: "https://github.com/f3ath/jessie" dev_dependencies: diff --git a/test/json_path_test.dart b/test/json_path_test.dart index ca4cfd5..e523298 100644 --- a/test/json_path_test.dart +++ b/test/json_path_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'dart:io'; -import 'package:jessie/jessie.dart'; +import 'package:json_path/json_path.dart'; import 'package:test/test.dart'; void main() { @@ -27,33 +27,17 @@ void main() { expect(store.select(json).single.value, json['store']); expect(store.select(json).single.path, r"$['store']"); }); + }); - test('Path with an index', () { - final firstBookTitle = JsonPath(r'$.store.book[0].title'); - expect(firstBookTitle.toString(), r"$['store']['book'][0]['title']"); - expect( - firstBookTitle.select(json).single.value, 'Sayings of the Century'); - expect(firstBookTitle.select(json).single.path, - r"$['store']['book'][0]['title']"); - }); - - test('All in array', () { - final allBooksInStore = JsonPath(r'$.store.book[*]'); - expect(allBooksInStore.toString(), r"$['store']['book'][*]"); - expect(allBooksInStore.select(json).length, 4); - expect( - allBooksInStore.select(json).first.value, json['store']['book'][0]); - expect(allBooksInStore.select(json).first.path, r"$['store']['book'][0]"); - expect(allBooksInStore.select(json).last.value, json['store']['book'][3]); - expect(allBooksInStore.select(json).last.path, r"$['store']['book'][3]"); - }); - - test('All values', () { + group('Wildcards', () { + test('All in root', () { final allInRoot = JsonPath(r'$.*'); expect(allInRoot.toString(), r'$.*'); expect(allInRoot.select(json).single.value, json['store']); expect(allInRoot.select(json).single.path, r"$['store']"); + }); + test('All in store', () { final allInStore = JsonPath(r'$.store.*'); expect(allInStore.toString(), r"$['store'].*"); expect(allInStore.select(json).length, 2); @@ -62,7 +46,9 @@ void main() { expect(allInStore.select(json).last.value, json['store']['bicycle']); expect(allInStore.select(json).last.path, r"$['store']['bicycle']"); }); + }); + group('Recursion', () { test('Recursive', () { final allNode = JsonPath(r'$..'); expect(allNode.toString(), r'$..'); @@ -73,16 +59,43 @@ void main() { expect(allNode.select(json).last.path, r"$['store']['bicycle']"); }); - test('Recursive with all fields', () { - final allValues = JsonPath(r'$..*'); - expect(allValues.toString(), r'$..*'); - expect(allValues.select(json).length, 27); - expect(allValues.select(json).first.value, json['store']); - expect(allValues.select(json).first.path, r"$['store']"); - expect( - allValues.select(json).last.value, json['store']['bicycle']['price']); - expect( - allValues.select(json).last.path, r"$['store']['bicycle']['price']"); + test('Recursive with all values', () { + final path = JsonPath(r'$..*'); + expect(path.toString(), r'$..*'); + expect(path.select(json).length, 27); + expect(path.select(json).first.value, json['store']); + expect(path.select(json).first.path, r"$['store']"); + expect(path.select(json).last.value, json['store']['bicycle']['price']); + expect(path.select(json).last.path, r"$['store']['bicycle']['price']"); + }); + + test('Every price tag', () { + final path = JsonPath(r'$..price'); + expect(path.toString(), r"$..['price']"); + expect(path.select(json).length, 5); +// expect(path.select(json).first.value, json['store']); +// expect(path.select(json).first.path, r"$['store']"); +// expect(path.select(json).last.value, json['store']['bicycle']['price']); +// expect(path.select(json).last.path, r"$['store']['bicycle']['price']"); + }); + }); + + group('Arrays', () { + test('Path with an index', () { + final path = JsonPath(r'$.store.book[0].title'); + expect(path.toString(), r"$['store']['book'][0]['title']"); + expect(path.select(json).single.value, 'Sayings of the Century'); + expect(path.select(json).single.path, r"$['store']['book'][0]['title']"); + }); + + test('All in array', () { + final path = JsonPath(r'$.store.book[*]'); + expect(path.toString(), r"$['store']['book'][*]"); + expect(path.select(json).length, 4); + expect(path.select(json).first.value, json['store']['book'][0]); + expect(path.select(json).first.path, r"$['store']['book'][0]"); + expect(path.select(json).last.value, json['store']['book'][3]); + expect(path.select(json).last.path, r"$['store']['book'][3]"); }); }); }