Skip to content

Commit

Permalink
Merge branch 'master' of github.com:jsonata-js/jsonata into jsonata-j…
Browse files Browse the repository at this point in the history
…s-master
  • Loading branch information
nddery committed Mar 5, 2024
2 parents 2dfa287 + b2a637e commit 3179acb
Show file tree
Hide file tree
Showing 16 changed files with 169 additions and 13 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 14
node-version: 20
- run: npm i
env:
GITHUB_TOKEN: ${{secrets.PUBLIC_PACKAGE_GITHUB_TOKEN}}
Expand All @@ -32,4 +32,4 @@ jobs:
GIT_COMMITTER_NAME: ${{steps.import_gpg.outputs.name}}
GIT_AUTHOR_EMAIL: ${{steps.import_gpg.outputs.email}}
GIT_COMMITTER_EMAIL: ${{steps.import_gpg.outputs.email}}
GITHUB_TOKEN: ${{secrets.PUBLIC_PACKAGE_GITHUB_TOKEN}}
GITHUB_TOKEN: ${{secrets.PUBLIC_PACKAGE_GITHUB_TOKEN}}
16 changes: 16 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Test JSONata
on:
push:
branches: [stedi-main]
pull_request:
branches: [stedi-main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- run: npm install
- run: npm test
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": "@stedi/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

0 comments on commit 3179acb

Please sign in to comment.