diff --git a/CHANGELOG.md b/CHANGELOG.md index abff10d..75cf222 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ ## [Unreleased] +## [0.0.0+dev.6] - 2020-08-01 +### Added +- Unions + ## [0.0.0+dev.5] - 2020-07-31 ### Added - Slice expression @@ -25,7 +29,8 @@ ### Added - Basic design draft -[Unreleased]: https://github.com/f3ath/jessie/compare/0.0.0+dev.5...HEAD +[Unreleased]: https://github.com/f3ath/jessie/compare/0.0.0+dev.6...HEAD +[0.0.0+dev.6]: https://github.com/f3ath/jessie/compare/0.0.0+dev.5...0.0.0+dev.6 [0.0.0+dev.5]: https://github.com/f3ath/jessie/compare/0.0.0+dev.4...0.0.0+dev.5 [0.0.0+dev.4]: https://github.com/f3ath/jessie/compare/0.0.0+dev.3...0.0.0+dev.4 [0.0.0+dev.3]: https://github.com/f3ath/jessie/compare/0.0.0+dev.2...0.0.0+dev.3 diff --git a/README.md b/README.md index 8119a0a..45b33b5 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ - [x] Wildcard (`$.store.*`) - [x] Square-bracket field notation (`['foo']`, `$['"some" \'special\' [chars]']`) - [x] Slice (`articles[1:10:2]`) -- [ ] Union (`book[0, 1]`, `book[author, title, price]`) -- [ ] Basic filtering (`book[?(@.price - 1)]`) -- [ ] Expressions? +- [x] Union (`book[0, 1]`, `book[author, title, price]`) +- [ ] Basic filtering (`book[?(@.price)]`) +- [ ] Expressions (`book[?(@.price * @.discount < 10)]`) [JSONPath]: https://goessner.net/articles/JsonPath/ \ No newline at end of file diff --git a/lib/src/selector/list_union.dart b/lib/src/selector/list_union.dart new file mode 100644 index 0000000..336e9a2 --- /dev/null +++ b/lib/src/selector/list_union.dart @@ -0,0 +1,20 @@ +import 'package:json_path/src/result.dart'; +import 'package:json_path/src/selector/selector.dart'; + +class ListUnion extends Selector { + ListUnion(this.keys); + + final List keys; + + @override + Iterable call(Iterable results) => results + .map((r) => (r.value is List) ? mapList(r.value, r.path) : []) + .expand((_) => _); + + Iterable mapList(List list, String path) => keys + .where((key) => key < list.length) + .map((key) => Result(list[key], path + '[$key]')); + + @override + String get expression => '[${keys.join(',')}]'; +} diff --git a/lib/src/selector/object_union.dart b/lib/src/selector/object_union.dart new file mode 100644 index 0000000..6383a82 --- /dev/null +++ b/lib/src/selector/object_union.dart @@ -0,0 +1,21 @@ +import 'package:json_path/src/quote.dart'; +import 'package:json_path/src/result.dart'; +import 'package:json_path/src/selector/selector.dart'; + +class ObjectUnion extends Selector { + ObjectUnion(this.keys); + + final List keys; + + @override + Iterable call(Iterable results) => results + .map((r) => (r.value is Map) ? map(r.value, r.path) : []) + .expand((_) => _); + + Iterable map(Map map, String path) => keys + .where(map.containsKey) + .map((key) => Result(map[key], path + '[${Quote(key)}]')); + + @override + String get expression => '[${keys.map((k) => Quote(k)).join(',')}]'; +} diff --git a/lib/src/selector/union.dart b/lib/src/selector/union.dart deleted file mode 100644 index e246ca4..0000000 --- a/lib/src/selector/union.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:json_path/src/result.dart'; -import 'package:json_path/src/selector/selector.dart'; - -class ArrayUnion extends Selector { - ArrayUnion(this.keys); - - final List keys; - @override - Iterable call(Iterable results) { - throw UnimplementedError(); - } - - @override - String get expression => '[${keys.join(',')}]'; - -} \ No newline at end of file diff --git a/lib/src/state.dart b/lib/src/state.dart index 665fd9b..aa83816 100644 --- a/lib/src/state.dart +++ b/lib/src/state.dart @@ -3,6 +3,8 @@ 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/list_union.dart'; +import 'package:json_path/src/selector/object_union.dart'; import 'package:json_path/src/selector/recursive.dart'; import 'package:json_path/src/selector/selector.dart'; import 'package:json_path/src/selector/slice.dart'; @@ -45,27 +47,35 @@ class Ready implements State { return multiValueBrackets(nodes); } - Slice multiValueBrackets(List nodes) { - int first; - int last; - int step; - var colons = 0; - nodes.forEach((node) { - if (node.value == ':') { - colons++; - return; - } - if (colons == 0) { - first = node.intValue; - return; - } - if (colons == 1) { - last = node.intValue; - return; - } - step = node.intValue; - }); - return Slice(first: first, last: last, step: step); + Selector multiValueBrackets(List nodes) { + if (nodes.any((node) => node.value == ':')) { + int first; + int last; + int step; + var colons = 0; + nodes.forEach((node) { + if (node.value == ':') { + colons++; + return; + } + if (colons == 0) { + first = node.intValue; + return; + } + if (colons == 1) { + last = node.intValue; + return; + } + step = node.intValue; + }); + return Slice(first: first, last: last, step: step); + } + final filtered = nodes.where((_) => _.value != ','); + if (nodes.first.isNumber) { + return ListUnion(filtered.map((_) => _.intValue).toList()); + } + return ObjectUnion( + filtered.map((_) => _.isQuoted ? _.unquoted : _.value).toList()); } Selector singleValueBrackets(Node node) { diff --git a/lib/src/tokenize.dart b/lib/src/tokenize.dart index f95c452..4af6f12 100644 --- a/lib/src/tokenize.dart +++ b/lib/src/tokenize.dart @@ -47,6 +47,7 @@ const _singles = [ '.', '*', ':', + ',', ]; const _doubles = [ diff --git a/pubspec.yaml b/pubspec.yaml index 72d273f..07f8a77 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: json_path -version: 0.0.0+dev.5 +version: 0.0.0+dev.6 description: JSONPath for Dart. JSONPath is XPath for JSON. It is a path in a JSON document. homepage: "https://github.com/f3ath/jessie" diff --git a/test/json_path_test.dart b/test/json_path_test.dart index 4797f30..d52d783 100644 --- a/test/json_path_test.dart +++ b/test/json_path_test.dart @@ -173,13 +173,32 @@ void main() { }); }); -// group('Union', () { -// test('Array', () { -//// final abc = 'abcdefg'.split(''); -// final union = JsonPath(r'$[2,3,5]'); -// expect(union.toString(), r'$[2,3,5]'); -// }); -// }); + group('Union', () { + test('List', () { + final abc = 'abcdefg'.split(''); + final union = JsonPath(r'$[2,3,100,5]'); + expect(union.toString(), r'$[2,3,100,5]'); + expect(union.select(abc).length, 3); + expect(union.select(abc).first.value, 'c'); + expect(union.select(abc).first.path, r'$[2]'); + expect(union.select(abc).last.value, 'f'); + expect(union.select(abc).last.path, r'$[5]'); + }); + test('Object', () { + final abc = { + 'a': 'A', + 'b': 'B', + 'c': 'C', + }; + final union = JsonPath(r"$['a','x',c]"); + expect(union.toString(), r"$['a','x','c']"); + expect(union.select(abc).length, 2); + expect(union.select(abc).first.value, 'A'); + expect(union.select(abc).first.path, r"$['a']"); + expect(union.select(abc).last.value, 'C'); + expect(union.select(abc).last.path, r"$['c']"); + }); + }); group('Wildcards', () { test('All in root', () {