From fd745c32a34dcf35b6fba82e22d12c0d6dc8fb20 Mon Sep 17 00:00:00 2001 From: f3ath Date: Tue, 28 Jul 2020 20:49:34 -0700 Subject: [PATCH] Union and wildcart --- README.md | 9 +++---- lib/src/selector/all_values.dart | 24 +++++++++++++++++++ lib/src/selector/selector.dart | 26 ++++++++++++++------ lib/src/state.dart | 12 ++++++++++ test/json_path_test.dart | 41 ++++++++++++++++++++++++++------ 5 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 lib/src/selector/all_values.dart diff --git a/README.md b/README.md index 7a373e8..a7105d1 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,11 @@ Jessie is a work-in-progress. Expect the API to change often. Feel free to join. ## Roadmap - [x] Basic selectors: fields, indices -- [ ] Recursive descent (`..`) -- [x] Wildcard (`*`) -- [ ] Subscript (`[:2]`) -- [ ] Slice (`[1:10:2]`) +- [x] Recursive descent (`$..`) +- [x] Wildcard (`$.store.*`) +- [ ] Square-bracket field notation (`['foo']`, `$['"some" \'special\' [chars]']`) +- [ ] Subscript (`books[:2]`) +- [ ] Slice (`articles[1:10:2]`) - [ ] Union (`book[0, 1]`, `book[author, title, price]`) - [ ] Basic filtering (`book[?(@.price - 1)]`) - [ ] Expressions? diff --git a/lib/src/selector/all_values.dart b/lib/src/selector/all_values.dart new file mode 100644 index 0000000..b99e771 --- /dev/null +++ b/lib/src/selector/all_values.dart @@ -0,0 +1,24 @@ +import 'package:jessie/src/quote.dart'; +import 'package:jessie/src/result.dart'; +import 'package:jessie/src/selector/selector.dart'; + +class AllValues extends Selector { + @override + Iterable call(Iterable results) => results.map((r) { + final val = r.value; + if (val is Map) { + return val.entries + .map((e) => Result(e.value, r.path + '[${Quote(e.key)}]')); + } + if (val is List) { + return val + .asMap() + .entries + .map((e) => Result(e.value, r.path + '[${e.key}]')); + } + return []; + }).expand((_) => _); + + @override + String get expression => '*'; +} diff --git a/lib/src/selector/selector.dart b/lib/src/selector/selector.dart index 04ab5f7..c4b712a 100644 --- a/lib/src/selector/selector.dart +++ b/lib/src/selector/selector.dart @@ -1,4 +1,6 @@ import 'package:jessie/src/result.dart'; +import 'package:jessie/src/selector/all_values.dart'; +import 'package:jessie/src/selector/recursive.dart'; /// Converts a set of results into a set of results abstract class Selector { @@ -14,19 +16,29 @@ abstract class Selector { String toString() => expression; /// Combines this expression with the [other] - Selector then(Selector other) => _Chain(this, other); + Selector then(Selector other) => Combine(this, other); } -class _Chain extends Selector { - _Chain(this.first, this.second); +/// Combines two selectors together +class Combine extends Selector { + Combine(this.left, this.right); - final Selector first; + /// Returns the rightmost selector in a chain + static Selector rightmost(Selector s) => + (s is Combine) ? rightmost(s.right) : s; - final Selector second; + final Selector left; + + final Selector right; @override - Iterable call(Iterable results) => second(first(results)); + Iterable call(Iterable results) => right(left(results)); @override - String get expression => '$first$second'; + String get expression { + /// Special case for the `*` element. + final actualLeft = rightmost(left); + final glue = (right is AllValues && (actualLeft is! Recursive)) ? '.' : ''; + return '$left$glue$right'; + } } diff --git a/lib/src/state.dart b/lib/src/state.dart index ee94e7c..b2ec251 100644 --- a/lib/src/state.dart +++ b/lib/src/state.dart @@ -1,16 +1,21 @@ 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'; +/// AST parser state abstract class State { + /// Processes the node. Returns the next state State process(Node node); + /// Selector made from the tree Selector get selector; } +/// Ready to process the next node class Ready implements State { Ready(this.selector); @@ -28,6 +33,10 @@ class Ready implements State { if (node.value == '..') { return Ready(selector.then(Recursive())); } + if (node.value == '*') { + return Ready(selector.then(AllValues())); + } + throw StateError('Got ${node.value} in $this'); } @@ -49,6 +58,9 @@ class AwaitingField implements State { @override State process(Node node) { + if (node.value == '*') { + return Ready(selector.then(AllValues())); + } return Ready(selector.then(Field(node.value))); } } diff --git a/test/json_path_test.dart b/test/json_path_test.dart index ea9c6d6..ca4cfd5 100644 --- a/test/json_path_test.dart +++ b/test/json_path_test.dart @@ -48,14 +48,41 @@ void main() { expect(allBooksInStore.select(json).last.path, r"$['store']['book'][3]"); }); + test('All values', () { + final allInRoot = JsonPath(r'$.*'); + expect(allInRoot.toString(), r'$.*'); + expect(allInRoot.select(json).single.value, json['store']); + expect(allInRoot.select(json).single.path, r"$['store']"); + + final allInStore = JsonPath(r'$.store.*'); + expect(allInStore.toString(), r"$['store'].*"); + expect(allInStore.select(json).length, 2); + expect(allInStore.select(json).first.value, json['store']['book']); + expect(allInStore.select(json).first.path, r"$['store']['book']"); + expect(allInStore.select(json).last.value, json['store']['bicycle']); + expect(allInStore.select(json).last.path, r"$['store']['bicycle']"); + }); + test('Recursive', () { - final everything = JsonPath(r'$..'); - expect(everything.toString(), r'$..'); - expect(everything.select(json).length, 8); - expect(everything.select(json).first.value, json); - expect(everything.select(json).first.path, r'$'); - expect(everything.select(json).last.value, json['store']['bicycle']); - expect(everything.select(json).last.path, r"$['store']['bicycle']"); + final allNode = JsonPath(r'$..'); + expect(allNode.toString(), r'$..'); + expect(allNode.select(json).length, 8); + expect(allNode.select(json).first.value, json); + expect(allNode.select(json).first.path, r'$'); + expect(allNode.select(json).last.value, json['store']['bicycle']); + 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']"); }); }); }