Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sync fork #33

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/jsonata.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
name: Build and publish JSONata
on:
push:
branches: [ master ]
branches: [ master, v1 ]
pull_request:
branches: [ master ]
branches: [ master, v1 ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [12.x, 14.x, 16.x, 18.x]
node-version: [12.x, 14.x, 16.x, 18.x, 20.x]
steps:
- name: Checkout
uses: actions/checkout@v3
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
#### 2.0.4 Maintenance Release

- Prevent writing to the object prototype or constructor (PR https://github.com/jsonata-js/jsonata/pull/676)
- Add upper/lower presentation format for am/pm in fromMillis (PR https://github.com/jsonata-js/jsonata/pull/644)
- Various documentation additions and corrections

#### 2.0.3 Maintenance Release

- Fix regex termination lexer (PR https://github.com/jsonata-js/jsonata/pull/623)
- Fix TypeScript definition (PR https://github.com/jsonata-js/jsonata/pull/633)

#### 2.0.2 Maintenance Release

- Typescript definition: fix return type of evaluate method (PR https://github.com/jsonata-js/jsonata/pull/615)

#### 2.0.1 Maintenance Release

- Small update to pick up README changes with 2.0.0 changes
Expand Down
2 changes: 1 addition & 1 deletion docs/construction.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ __Examples__

## Object constructors

In a similar manner to the way arrays can be constructed, JSON objects can also be constructed in the output. At any point in a location path where a field reference is expected, a pair of braces `{}` containing key/value pairs separated by commas, with each key and value separated by a colon: `{key1: value2, key2:value2}`. The keys and values can either be literals or can be expressions. The key must either be a string or an expression that evaluates to a string.
In a similar manner to the way arrays can be constructed, JSON objects can also be constructed in the output. At any point in a location path where a field reference is expected, a pair of braces `{}` containing key/value pairs separated by commas, with each key and value separated by a colon: `{key1: value1, key2:value2}`. The keys and values can either be literals or can be expressions. The key must either be a string or an expression that evaluates to a string.

When an object constructor follows an expression that selects multiple values, the object constructor will create a single object that contains a key/value pair for each of those context values. If an array of objects is required (one for each context value), then the object constructor should immediately follow the dot '.' operator.

Expand Down
2 changes: 1 addition & 1 deletion docs/numeric-operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ __Example__
`5 + 2` => `7`


## `-` (Substraction/Negation)
## `-` (Subtraction/Negation)

The subtraction operator subtracts the RHS value from the LHS value to produce the numerical difference It is an error if either operand is not a number.

Expand Down
17 changes: 16 additions & 1 deletion docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,22 @@ JSONata is a lightweight query and transformation language for JSON data. Inspir
* Install the module from [NPM](https://www.npmjs.com/package/jsonata)
* Fork the repo on [GitHub](https://github.com/jsonata-js/jsonata)

## Implementations of JSONata

The following are known implementations of JSONata in addition to the primary implementation in JavaScript in the above repo.

|Language|Link|Notes|JSONata version|
|---|---|---|---|
|C|https://github.com/qlyoung/jsonata-c|Runs JSONata in embedded JS engine|1.8.3|
|Go|https://github.com/blues/jsonata-go|Native implementation|1.5.4|
|Go|https://github.com/yxuco/gojsonata|Native implementation| |
|Java|https://github.com/IBM/JSONata4Java|Native implementation| |
|Java|https://github.com/dashjoin/jsonata-java|Native port of reference|2.0.3|
|.NET|https://github.com/mikhail-barg/jsonata.net.native|Native implementation|1.8.5|
|Python|https://github.com/qlyoung/pyjsonata|API bindings based on C bindings|1.8.3|
|Rust|https://github.com/johanventer/jsonata-rust|Implementation work in progress| |
|Rust|https://github.com/Stedi/jsonata-rs|Actively-developed fork of jsonata-rust| |

## Find out more

* Introduction at [London Node User Group meetup](https://www.youtube.com/watch?v=TDWf6R8aqDo)
* IBM developerWorks [Tech Talk](https://www.youtube.com/watch?v=ZRtlkIj0uDY)
2 changes: 1 addition & 1 deletion docs/predicate.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ At any step in a location path, the selected items can be filtered using a predi

## Singleton array and value equivalence

Within a JSONata expression or subexpression, any value (which is not itself an array) and an array containing just that value are deemed to be equivalent. This allows the language to be composable such that location paths that extract a single value from and object and location paths that extract multiple values from arrays can both be used as inputs to other expressions without needing to use different syntax for the two forms.
Within a JSONata expression or subexpression, any value (which is not itself an array) and an array containing just that value are deemed to be equivalent. This allows the language to be composable such that location paths that extract a single value from an object and location paths that extract multiple values from arrays can both be used as inputs to other expressions without needing to use different syntax for the two forms.

Consider the following examples:

Expand Down
2 changes: 1 addition & 1 deletion docs/regex.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Regexes are often used in query predicates (filter expressions) when selecting o

`path.to.object[stringProperty ~> /regex/]`

The `~>` is the [chain operator](control-operators#chain), and its use here implies that the result of `/regex/` is a function. We'll see below that this is in fact the case.
The `~>` is the [chain operator](other-operators#-chain), and its use here implies that the result of `/regex/` is a function. We'll see below that this is in fact the case.

__Examples__

Expand Down
2 changes: 1 addition & 1 deletion docs/sorting-grouping.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Arrays contain an ordered collection of values. If you need to re-order the val

2. Using the [order-by](path-operators#order-by-) operator.

The [order-by](path-operators#order-by-) operator is a convenient syntax that can used directly in a path expression to sort the result sequences in ascending or descending order. The [`$sort()`](array-functions#sort) function requires more syntax to be written, but is more flexible and supports custom comparator functions.
The [order-by](path-operators#order-by-) operator is a convenient syntax that can be used directly in a path expression to sort the result sequences in ascending or descending order. The [`$sort()`](array-functions#sort) function requires more syntax to be written, but is more flexible and supports custom comparator functions.

## Grouping

Expand Down
9 changes: 7 additions & 2 deletions jsonata.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
// Project: https://github.com/jsonata-js/jsonata
// Definitions by: Nick <https://github.com/nick121212> and Michael M. Tiller <https://github.com/xogeny>

declare function jsonata(str: string): jsonata.Expression;
declare function jsonata(str: string, options?: jsonata.JsonataOptions): jsonata.Expression;
declare namespace jsonata {

interface JsonataOptions {
recover?: boolean,
RegexEngine?: RegExp
}

interface ExprNode {
type: string;
value?: any;
Expand All @@ -15,7 +20,7 @@ declare namespace jsonata {
steps?: ExprNode[];
expressions?: ExprNode[];
stages?: ExprNode[];
lhs?: ExprNode;
lhs?: ExprNode[];
rhs?: ExprNode;
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jsonata",
"version": "2.0.1",
"version": "2.0.4",
"description": "JSON query and transformation language",
"module": "jsonata.js",
"main": "jsonata.js",
Expand Down
7 changes: 7 additions & 0 deletions src/datetime.js
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,13 @@ const dateTime = (function () {
if (offset === 0 && markerSpec.presentation2 === 't') {
componentValue = 'Z';
}
} else if (markerSpec.component === 'P') {
// §9.8.4.7 Formatting Other Components
// Formatting P for am/pm
// getDateTimeFragment() always returns am/pm lower case so check for UPPER here
if (markerSpec.names === tcase.UPPER) {
componentValue = componentValue.toUpperCase();
}
}
return componentValue;
};
Expand Down
10 changes: 9 additions & 1 deletion src/jsonata.js
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,13 @@ var jsonata = (function() {
}
for(var ii = 0; ii < matches.length; ii++) {
var match = matches[ii];
if (match && (match.isPrototypeOf(result) || match instanceof Object.constructor)) {
throw {
code: "D1010",
stack: (new Error()).stack,
position: expr.position
};
}
// evaluate the update value for each match
var update = await evaluate(expr.update, match, environment);
// update must be an object
Expand Down Expand Up @@ -1539,7 +1546,7 @@ var jsonata = (function() {
if (typeof err.token == 'undefined' && typeof proc.token !== 'undefined') {
err.token = proc.token;
}
err.position = proc.position;
err.position = proc.position || err.position;
}
throw err;
}
Expand Down Expand Up @@ -1972,6 +1979,7 @@ var jsonata = (function() {
"T1007": "Attempted to partially apply a non-function. Did you mean ${{{token}}}?",
"T1008": "Attempted to partially apply a non-function",
"D1009": "Multiple key definitions evaluate to same key: {{value}}",
"D1010": "Attempted to access the Javascript object prototype", // Javascript specific
"T1010": "The matcher function argument passed to function {{token}} does not return the correct object structure",
"T2001": "The left side of the {{token}} operator must evaluate to a number",
"T2002": "The right side of the {{token}} operator must evaluate to a number",
Expand Down
16 changes: 15 additions & 1 deletion src/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,23 @@ const parser = (() => {
var depth = 0;
var pattern;
var flags;

var isClosingSlash = function (position) {
if (path.charAt(position) === '/' && depth === 0) {
var backslashCount = 0;
while (path.charAt(position - (backslashCount + 1)) === '\\') {
backslashCount++;
}
if (backslashCount % 2 === 0) {
return true;
}
}
return false;
};

while (position < length) {
var currentChar = path.charAt(position);
if (currentChar === '/' && path.charAt(position - 1) !== '\\' && depth === 0) {
if (isClosingSlash(position)) {
// end of regex found
pattern = path.substring(start, position);
if (pattern === '') {
Expand Down
60 changes: 60 additions & 0 deletions test/implementation-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,38 @@ describe("Tests that are specific to a Javascript runtime", () => {
});
});

describe("empty regex: Escaped termination", function() {
it("should throw error", function() {
expect(function() {
var expr = jsonata("/\\/");
expr.evaluate();
})
.to.throw()
.to.deep.contain({ position: 3, code: "S0302" });
});
});

describe("empty regex: Escaped termination", function() {
it("should throw error", function() {
expect(function() {
var expr = jsonata("/\\\\\\/");
expr.evaluate();
})
.to.throw()
.to.deep.contain({ position: 5, code: "S0302" });
});
});

describe("Functions - $match", function() {
describe('$match("test escape \\\\", /\\\\/)', function() {
it("should find \\", async function() {
var expr = jsonata('$match("test escape \\\\", /\\\\/)');
var result = await expr.evaluate();
var expected = { match: "\\", index: 12, groups: []};
expect(result).to.deep.equal(expected);
});
});

describe('$match("ababbabbcc",/ab/)', function() {
it("should return result object", async function() {
var expr = jsonata('$match("ababbabbcc",/ab/)');
Expand Down Expand Up @@ -924,6 +955,35 @@ describe("Tests that are specific to a Javascript runtime", () => {
});
});
});
describe("Expressions that attempt to pollute the object prototype", function() {
it("should throw an error with __proto__", async function() {
const expr = jsonata('{} ~> | __proto__ | {"is_admin": true} |');
expect(
expr.evaluate()
).to.eventually.be.rejected.to.deep.contain({
position: 7,
code: "D1010",
});
});
it("should throw an error with __lookupGetter__", async function() {
const expr = jsonata('{} ~> | __lookupGetter__("__proto__")() | {"is_admin": true} |');
expect(
expr.evaluate()
).to.eventually.be.rejected.to.deep.contain({
position: 7,
code: "D1010",
});
});
it("should throw an error with constructor", async function() {
const expr = jsonata('{} ~> | constructor | {"is_admin": true} |');
expect(
expr.evaluate()
).to.eventually.be.rejected.to.deep.contain({
position: 7,
code: "D1010",
});
});
});
});

describe("Test that yield platform specific results", () => {
Expand Down
16 changes: 16 additions & 0 deletions test/test-suite/groups/function-fromMillis/formatDateTime.json
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,22 @@
"2018-10-21T13:05:00.000Z"
]
},
{
"function": "#fromMillis",
"category": "Upper case AM/PM presentation",
"description": "am/pm presentation should be set to uppercase AM",
"expr": "$fromMillis(1521801216617, '[F], [D]/[M]/[Y] [h]:[m]:[s] [PN]')",
"data": {},
"result": "friday, 23/3/2018 10:33:36 AM"
},
{
"function": "#fromMillis",
"category": "Lower case AM/PM presentation",
"description": "am/pm presentation should be set to lowercase am",
"expr": "$fromMillis(1521801216617, '[F], [D]/[M]/[Y] [h]:[m]:[s] [Pn]')",
"data": {},
"result": "friday, 23/3/2018 10:33:36 am"
},
{
"function": "#fromMillis",
"category": "error",
Expand Down