Skip to content

Commit ab7a103

Browse files
Merge pull request #24 from TotalTechGeek/feat/support-dot-prop-syntax
Add support for dot-prop syntax to resolve #23
2 parents 0578816 + 408d447 commit ab7a103

File tree

5 files changed

+66
-6
lines changed

5 files changed

+66
-6
lines changed

defaultMethods.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { build, buildString } from './compiler.js'
88
import chainingSupported from './utilities/chainingSupported.js'
99
import InvalidControlInput from './errors/InvalidControlInput.js'
1010
import YieldingIterators from './yieldingIterators.js'
11+
import { splitPath } from './utilities/splitPath.js'
1112

1213
function isDeterministic (method, engine, buildState) {
1314
if (Array.isArray(method)) {
@@ -161,7 +162,8 @@ const defaultMethods = {
161162
get: {
162163
method: ([data, key, defaultValue], context, above, engine) => {
163164
const notFound = defaultValue === undefined ? null : defaultValue
164-
const subProps = String(key).split('.')
165+
166+
const subProps = splitPath(String(key))
165167
for (let i = 0; i < subProps.length; i++) {
166168
if (data === null || data === undefined) {
167169
return notFound
@@ -203,7 +205,7 @@ const defaultMethods = {
203205
}
204206
return null
205207
}
206-
const subProps = String(key).split('.')
208+
const subProps = splitPath(String(key))
207209
for (let i = 0; i < subProps.length; i++) {
208210
if (context === null || context === undefined) {
209211
return notFound
@@ -810,7 +812,7 @@ defaultMethods.get.compile = function (data, buildState) {
810812
if (key && typeof key === 'object') return false
811813

812814
key = key.toString()
813-
const pieces = key.split('.')
815+
const pieces = splitPath(key)
814816
if (!chainingSupported) {
815817
return `(((a,b) => (typeof a === 'undefined' || a === null) ? b : a)(${pieces.reduce(
816818
(text, i) => {
@@ -854,7 +856,7 @@ defaultMethods.var.compile = function (data, buildState) {
854856
buildState.useContext = true
855857
return false
856858
}
857-
const pieces = key.split('.')
859+
const pieces = splitPath(key)
858860
const [top] = pieces
859861
buildState.varTop.add(top)
860862
// support older versions of node

general.test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,21 @@ describe('Various Test Cases', () => {
117117
it('get operator w/ object key as var', async () => {
118118
for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { get: [{ var: 'selected' }, { var: 'key' }] }, { selected: { b: 2 }, key: 'b' }, 2)
119119
})
120+
121+
it('is able to handle simple path escaping', async () => {
122+
for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { get: [{ var: 'selected' }, 'b\\.c'] }, { selected: { 'b.c': 2 } }, 2)
123+
})
124+
125+
it('is able to handle simple path escaping in a variable', async () => {
126+
for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { get: [{ var: 'selected' }, { var: 'key' }] }, { selected: { 'b.c': 2 }, key: 'b\\.c' }, 2)
127+
})
128+
129+
it('is able to handle path escaping in a var call', async () => {
130+
for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { var: 'hello\\.world' }, { 'hello.world': 2 }, 2)
131+
})
132+
133+
it('is able to handle path escaping with multiple escapes', async () => {
134+
for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { var: '\\foo' }, { '\\foo': 2 }, 2)
135+
for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { var: '\\\\foo' }, { '\\foo': 2 }, 2)
136+
})
120137
})

index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import EngineObject from './structures/EngineObject.js'
99
import Constants from './constants.js'
1010
import defaultMethods from './defaultMethods.js'
1111
import { asLogicSync, asLogicAsync } from './asLogic.js'
12+
import { splitPath } from './utilities/splitPath.js'
1213

14+
export { splitPath }
1315
export { LogicEngine }
1416
export { AsyncLogicEngine }
1517
export { Compiler }
@@ -20,4 +22,4 @@ export { defaultMethods }
2022
export { asLogicSync }
2123
export { asLogicAsync }
2224

23-
export default { LogicEngine, AsyncLogicEngine, Compiler, YieldStructure, EngineObject, Constants, defaultMethods, asLogicSync, asLogicAsync }
25+
export default { LogicEngine, AsyncLogicEngine, Compiler, YieldStructure, EngineObject, Constants, defaultMethods, asLogicSync, asLogicAsync, splitPath }

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "json-logic-engine",
3-
"version": "1.2.11",
3+
"version": "1.3.0",
44
"description": "Construct complex rules with JSON & process them.",
55
"main": "./dist/cjs/index.js",
66
"module": "./dist/esm/index.js",

utilities/splitPath.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
/**
3+
* Splits a path string into an array of parts.
4+
*
5+
* @example splitPath('a.b.c') // ['a', 'b', 'c']
6+
* @example splitPath('a\\.b.c') // ['a.b', 'c']
7+
* @example splitPath('a\\\\.b.c') // ['a\\', 'b', 'c']
8+
* @example splitPath('a\\\\\\.b.c') // ['a\\.b', 'c']
9+
* @example splitPath('hello') // ['hello']
10+
* @example splitPath('hello\\') // ['hello\\']
11+
* @example splitPath('hello\\\\') // ['hello\\']
12+
*
13+
* @param {string} str
14+
* @param {string} separator
15+
* @returns {string[]}
16+
*/
17+
export function splitPath (str, separator = '.', escape = '\\') {
18+
const parts = []
19+
let current = ''
20+
21+
for (let i = 0; i < str.length; i++) {
22+
const char = str[i]
23+
if (char === escape) {
24+
if (str[i + 1] === separator) {
25+
current += separator
26+
i++
27+
} else if (str[i + 1] === escape) {
28+
current += escape
29+
i++
30+
} else current += escape
31+
} else if (char === separator) {
32+
parts.push(current)
33+
current = ''
34+
} else current += char
35+
}
36+
parts.push(current)
37+
38+
return parts
39+
}

0 commit comments

Comments
 (0)