Skip to content

Commit

Permalink
Merge pull request #24 from TotalTechGeek/feat/support-dot-prop-syntax
Browse files Browse the repository at this point in the history
Add support for dot-prop syntax to resolve #23
  • Loading branch information
TotalTechGeek committed Nov 24, 2023
2 parents 0578816 + 408d447 commit ab7a103
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 6 deletions.
10 changes: 6 additions & 4 deletions defaultMethods.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { build, buildString } from './compiler.js'
import chainingSupported from './utilities/chainingSupported.js'
import InvalidControlInput from './errors/InvalidControlInput.js'
import YieldingIterators from './yieldingIterators.js'
import { splitPath } from './utilities/splitPath.js'

function isDeterministic (method, engine, buildState) {
if (Array.isArray(method)) {
Expand Down Expand Up @@ -161,7 +162,8 @@ const defaultMethods = {
get: {
method: ([data, key, defaultValue], context, above, engine) => {
const notFound = defaultValue === undefined ? null : defaultValue
const subProps = String(key).split('.')

const subProps = splitPath(String(key))
for (let i = 0; i < subProps.length; i++) {
if (data === null || data === undefined) {
return notFound
Expand Down Expand Up @@ -203,7 +205,7 @@ const defaultMethods = {
}
return null
}
const subProps = String(key).split('.')
const subProps = splitPath(String(key))
for (let i = 0; i < subProps.length; i++) {
if (context === null || context === undefined) {
return notFound
Expand Down Expand Up @@ -810,7 +812,7 @@ defaultMethods.get.compile = function (data, buildState) {
if (key && typeof key === 'object') return false

key = key.toString()
const pieces = key.split('.')
const pieces = splitPath(key)
if (!chainingSupported) {
return `(((a,b) => (typeof a === 'undefined' || a === null) ? b : a)(${pieces.reduce(
(text, i) => {
Expand Down Expand Up @@ -854,7 +856,7 @@ defaultMethods.var.compile = function (data, buildState) {
buildState.useContext = true
return false
}
const pieces = key.split('.')
const pieces = splitPath(key)
const [top] = pieces
buildState.varTop.add(top)
// support older versions of node
Expand Down
17 changes: 17 additions & 0 deletions general.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,21 @@ describe('Various Test Cases', () => {
it('get operator w/ object key as var', async () => {
for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { get: [{ var: 'selected' }, { var: 'key' }] }, { selected: { b: 2 }, key: 'b' }, 2)
})

it('is able to handle simple path escaping', async () => {
for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { get: [{ var: 'selected' }, 'b\\.c'] }, { selected: { 'b.c': 2 } }, 2)
})

it('is able to handle simple path escaping in a variable', async () => {
for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { get: [{ var: 'selected' }, { var: 'key' }] }, { selected: { 'b.c': 2 }, key: 'b\\.c' }, 2)
})

it('is able to handle path escaping in a var call', async () => {
for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { var: 'hello\\.world' }, { 'hello.world': 2 }, 2)
})

it('is able to handle path escaping with multiple escapes', async () => {
for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { var: '\\foo' }, { '\\foo': 2 }, 2)
for (const engine of [...normalEngines, ...permissiveEngines]) await testEngine(engine, { var: '\\\\foo' }, { '\\foo': 2 }, 2)
})
})
4 changes: 3 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import EngineObject from './structures/EngineObject.js'
import Constants from './constants.js'
import defaultMethods from './defaultMethods.js'
import { asLogicSync, asLogicAsync } from './asLogic.js'
import { splitPath } from './utilities/splitPath.js'

export { splitPath }
export { LogicEngine }
export { AsyncLogicEngine }
export { Compiler }
Expand All @@ -20,4 +22,4 @@ export { defaultMethods }
export { asLogicSync }
export { asLogicAsync }

export default { LogicEngine, AsyncLogicEngine, Compiler, YieldStructure, EngineObject, Constants, defaultMethods, asLogicSync, asLogicAsync }
export default { LogicEngine, AsyncLogicEngine, Compiler, YieldStructure, EngineObject, Constants, defaultMethods, asLogicSync, asLogicAsync, splitPath }
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "json-logic-engine",
"version": "1.2.11",
"version": "1.3.0",
"description": "Construct complex rules with JSON & process them.",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
Expand Down
39 changes: 39 additions & 0 deletions utilities/splitPath.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

/**
* Splits a path string into an array of parts.
*
* @example splitPath('a.b.c') // ['a', 'b', 'c']
* @example splitPath('a\\.b.c') // ['a.b', 'c']
* @example splitPath('a\\\\.b.c') // ['a\\', 'b', 'c']
* @example splitPath('a\\\\\\.b.c') // ['a\\.b', 'c']
* @example splitPath('hello') // ['hello']
* @example splitPath('hello\\') // ['hello\\']
* @example splitPath('hello\\\\') // ['hello\\']
*
* @param {string} str
* @param {string} separator
* @returns {string[]}
*/
export function splitPath (str, separator = '.', escape = '\\') {
const parts = []
let current = ''

for (let i = 0; i < str.length; i++) {
const char = str[i]
if (char === escape) {
if (str[i + 1] === separator) {
current += separator
i++
} else if (str[i + 1] === escape) {
current += escape
i++
} else current += escape
} else if (char === separator) {
parts.push(current)
current = ''
} else current += char
}
parts.push(current)

return parts
}

0 comments on commit ab7a103

Please sign in to comment.