Skip to content

Commit

Permalink
New functions (#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
f3ath committed May 31, 2024
1 parent b7a1ab1 commit a085de5
Show file tree
Hide file tree
Showing 25 changed files with 225 additions and 79 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.7.2] - 2024-05-30
### Added
- New functions: `key()` and `index()`

## [0.7.1] - 2024-03-02
### Changed
- Bumped the CTS to the latest
Expand Down Expand Up @@ -185,6 +189,7 @@ Previously, no modification would be made and no errors/exceptions thrown.
### Added
- Basic design draft

[0.7.2]: https://github.com/f3ath/jessie/compare/0.7.1...0.7.2
[0.7.1]: https://github.com/f3ath/jessie/compare/0.7.0...0.7.1
[0.7.0]: https://github.com/f3ath/jessie/compare/0.6.6...0.7.0
[0.6.6]: https://github.com/f3ath/jessie/compare/0.6.5...0.6.6
Expand Down
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,26 @@ To use it:
For more details see the included example.

This package comes with some non-standard functions which you might find useful.
To use them, import `package:json_path/fun_extra.dart`.
- `count(<NodeList>)` - returns the number of nodes selected by the argument
- `index(<SingularNodeList>)` - returns the index under which the array element is referenced by the parent array
- `key(<SingularNodeList>)` - returns the key under which the object element is referenced by the parent object
- `is_array(<Maybe>)` - returns true if the value is an array
- `is_boolean(<Maybe>)` - returns true if the value is a boolean
- `is_number(<Maybe>)` - returns true if the value is a number
- `is_object(<Maybe>)` - returns true if the value is an object
- `is_string(<Maybe>)` - returns true if the value is a string
- `reverse(<Maybe>)` - reverses the string
- `siblings(<NodeList>)` - returns the siblings for the nodes
- `xor(<bool>, <bool>)` - returns the XOR of two booleans arguments

To use them, import `package:json_path/fun_extra.dart` and supply them to the `JsonPath()` constructor:

```dart
final jsonPath = JsonPathParser(functions: [
const Key(),
const Reverse(),
]).parse(r'$[?key(@) == reverse(key(@))]');
```
## References
- [Standard development](https://github.com/ietf-wg-jsonpath/draft-ietf-jsonpath-base)
- [Feature comparison matrix](https://cburgmer.github.io/json-path-comparison/)
Expand Down
2 changes: 2 additions & 0 deletions lib/fun_extra.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/// A collection of semi-useful non-standard functions for JSONPath.
library fun_extra;

export 'package:json_path/src/fun/extra/index.dart';
export 'package:json_path/src/fun/extra/is_array.dart';
export 'package:json_path/src/fun/extra/is_boolean.dart';
export 'package:json_path/src/fun/extra/is_number.dart';
export 'package:json_path/src/fun/extra/is_object.dart';
export 'package:json_path/src/fun/extra/is_string.dart';
export 'package:json_path/src/fun/extra/key.dart';
export 'package:json_path/src/fun/extra/reverse.dart';
export 'package:json_path/src/fun/extra/siblings.dart';
export 'package:json_path/src/fun/extra/xor.dart';
2 changes: 2 additions & 0 deletions lib/src/expression/nodes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class SingularNodeList with NodeList {

final NodeList _nodes;

Node? get node => length == 1 ? first : null;

@override
Iterator<Node<Object?>> get iterator => _nodes.iterator;
}
Expand Down
15 changes: 15 additions & 0 deletions lib/src/fun/extra/index.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:json_path/fun_sdk.dart';

/// Returns the index under which the node referenced by the argument
/// is found in the parent array.
/// If the parent is not an array, returns [Nothing].
/// If the argument does not reference a single node, returns [Nothing].
class Index implements Fun1<Maybe, SingularNodeList> {
const Index();

@override
final name = 'index';

@override
Maybe call(SingularNodeList nodes) => Just(nodes.node?.index).type<int>();
}
2 changes: 1 addition & 1 deletion lib/src/fun/extra/is_array.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:json_path/fun_sdk.dart';

/// Checks if the value is a JSON array.
/// Returns true if the value is a JSON array.
class IsArray implements Fun1<bool, Maybe> {
const IsArray();

Expand Down
2 changes: 1 addition & 1 deletion lib/src/fun/extra/is_boolean.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:json_path/fun_sdk.dart';

/// Checks if the value is a JSON boolean.
/// Returns true if the value is a JSON boolean.
class IsBoolean implements Fun1<bool, Maybe> {
const IsBoolean();

Expand Down
2 changes: 1 addition & 1 deletion lib/src/fun/extra/is_number.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:json_path/fun_sdk.dart';

/// Checks if the value is a JSON number.
/// Returns true if the value is a JSON number.
class IsNumber implements Fun1<bool, Maybe> {
const IsNumber();

Expand Down
2 changes: 1 addition & 1 deletion lib/src/fun/extra/is_object.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:json_path/fun_sdk.dart';

/// Checks if the value is a JSON object.
/// Returns true if the value is a JSON object.
class IsObject implements Fun1<bool, Maybe> {
const IsObject();

Expand Down
2 changes: 1 addition & 1 deletion lib/src/fun/extra/is_string.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:json_path/fun_sdk.dart';

/// Checks if the value is a JSON string.
/// Returns true if the value is a JSON string.
class IsString implements Fun1<bool, Maybe> {
const IsString();

Expand Down
15 changes: 15 additions & 0 deletions lib/src/fun/extra/key.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:json_path/fun_sdk.dart';

/// Returns the key under which the node referenced by the argument
/// is found in the parent object.
/// If the parent is not an object, returns [Nothing].
/// If the argument does not reference a single node, returns [Nothing].
class Key implements Fun1<Maybe, SingularNodeList> {
const Key();

@override
final name = 'key';

@override
Maybe call(SingularNodeList nodes) => Just(nodes.node?.key).type<String>();
}
26 changes: 18 additions & 8 deletions lib/src/fun/fun_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,13 @@ class FunFactory {

Expression<T> _any1<T extends Object>(String name, Expression a0) {
final f = _getFun1<T>(name);
final cast0 = cast(a0,
value: f is Fun1<T, Maybe>,
logical: f is Fun1<T, bool>,
nodes: f is Fun1<T, NodeList>);
final cast0 = cast(
a0,
value: f is Fun1<T, Maybe>,
logical: f is Fun1<T, bool>,
node: f is Fun1<T, SingularNodeList>,
nodes: f is Fun1<T, NodeList>,
);
return cast0.map(f.call);
}

Expand All @@ -60,12 +63,14 @@ class FunFactory {
a0,
value: f is Fun2<T, Maybe, Object>,
logical: f is Fun2<T, bool, Object>,
node: f is Fun2<T, SingularNodeList, Object>,
nodes: f is Fun2<T, NodeList, Object>,
);
final cast1 = cast(
a1,
value: f is Fun2<T, Object, Maybe>,
logical: f is Fun2<T, Object, bool>,
node: f is Fun2<T, Object, SingularNodeList>,
nodes: f is Fun2<T, Object, NodeList>,
);
return cast0.merge(cast1, f.call);
Expand All @@ -84,16 +89,21 @@ class FunFactory {
}

static Expression cast(Expression arg,
{required bool value, required bool logical, required bool nodes}) {
{required bool value,
required bool logical,
required bool node,
required bool nodes}) {
if (value) {
if (arg is Expression<Maybe>) return arg;
if (arg is Expression<SingularNodeList>) return arg.map((v) => v.asValue);
}
if (logical) {
} else if (logical) {
if (arg is Expression<bool>) return arg;
if (arg is Expression<NodeList>) return arg.map((v) => v.asLogical);
} else if (node) {
if (arg is Expression<SingularNodeList>) return arg;
} else if (nodes) {
if (arg is Expression<NodeList>) return arg;
}
if (nodes && arg is Expression<NodeList>) return arg;
throw Exception('Arg type mismatch');
}
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: json_path
version: 0.7.1
version: 0.7.2
description: "Implementation of RFC 9535 - JSONPath: Query Expressions for JSON. Reads and writes values in parsed JSON objects using queries like `$.store.book[2].price`."
homepage: "https://github.com/f3ath/jessie"

Expand Down
64 changes: 0 additions & 64 deletions test/cases/extra/cases.json

This file was deleted.

10 changes: 10 additions & 0 deletions test/cases/extra/count.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"tests": [
{
"name": "count(siblings(@))",
"selector" : "$..[?count(siblings(@)) == 1]",
"document" : {"a": {"b": "x", "d": "x"}},
"result": ["x", "x"]
}
]
}
21 changes: 21 additions & 0 deletions test/cases/extra/index.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"tests": [
{
"name": "index()",
"selector" : "$[?index(@) == 1]",
"document" : [ "A", "B"],
"result": ["B"]
},
{
"name": "index(), does not work on objects",
"selector" : "$[?index(@) == 0]",
"document" : {"0": "A", "1": "B"},
"result": []
},
{
"name": "index(), non singular",
"selector" : "$[?index(@.*) == 0]",
"invalid_selector": true
}
]
}
11 changes: 11 additions & 0 deletions test/cases/extra/is_array.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

{
"tests": [
{
"name": "is_array(@)",
"selector" : "$[?is_array(@)]",
"document" : [1, true, {}, [42], "foo", {"a": "b"}],
"result": [[42]]
}
]
}
10 changes: 10 additions & 0 deletions test/cases/extra/is_boolean.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"tests": [
{
"name": "is_boolean",
"selector" : "$[?is_boolean(@)]",
"document" : [1, true, {}, [42], "foo", {"a": "b"}, false],
"result": [true, false]
}
]
}
10 changes: 10 additions & 0 deletions test/cases/extra/is_number.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"tests": [
{
"name": "is_number",
"selector" : "$[?is_number(@)]",
"document" : [1, true, {}, [42], 3.14, "foo", {"a": "b"}],
"result": [1, 3.14]
}
]
}
10 changes: 10 additions & 0 deletions test/cases/extra/is_object.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"tests": [
{
"name": "is_object(@)",
"selector" : "$[?is_object(@)]",
"document" : [1, true, {}, [42], "foo", {"a": "b"}],
"result": [{}, {"a": "b"}]
}
]
}
10 changes: 10 additions & 0 deletions test/cases/extra/is_string.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"tests": [
{
"name": "is_string",
"selector" : "$[?is_string(@)]",
"document" : [1, true, {}, [42], "foo", {"a": "b"}],
"result": ["foo"]
}
]
}
27 changes: 27 additions & 0 deletions test/cases/extra/key.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"tests": [
{
"name": "key()",
"selector" : "$[?key(@) == 'a']",
"document" : {"a": "A", "b": "B"},
"result": ["A"]
},
{
"name": "key(), palindromic keys",
"selector" : "$[?key(@) == reverse(key(@))]",
"document" : {"foo": "FOO", "bar": "BAR", "bab": "BAB", "": "", "a": "A"},
"result": ["BAB","","A"]
},
{
"name": "key(), does not work on arrays",
"selector" : "$[?key(@) == 0]",
"document" : ["A", "B"],
"result": []
},
{
"name": "key(), non singular",
"selector" : "$[?key(@.*) == 'a']",
"invalid_selector": true
}
]
}
Loading

0 comments on commit a085de5

Please sign in to comment.