Closed enchorb closed 1 year ago
Hey @enchorb, I might take a look into this,
The main reason that it doesn't give you the raw JavaScript that it compiles down into is that in certain cases,
map
and reduce
.Could you potentially elaborate on your use case?
I might be able to offer a simpler solution based on your requirements.
Hey @TotalTechGeek,
Thanks for the quick response. I'll give a brief overview of the use case:
1) Users from client send can create custom formulas (saved in a formulas table in a jsonb column) for converting raw data in DB tables
2) In the DB before the call to fetch the raw data was retrieve the users formula and then replace it in the select statement
This is why having the function representation would be useful, especially for arithmetic operations
Example (pseudocode)
const engine = new LogicEngine();
engine.addModule('', Math, { deterministic: true, sync: true });
const logic = db.from('formulas').select(query to get formula which is saved logic from earlier comment)
const formula = engine.getFunction(logic, { test: '(raw->'jsonProprtyName')::numeric' })
db.from('tableName').select(`${formula} AS value`)
Hey, so I don't know when I'll get around to reworking the compiler, but it sounds like you're mostly just aiming for a expression representation, which is a bit simpler.
Would something like this satisfy your use case?
/**
* This method takes JSON Logic and converts it to a reasonable expression that
* represents the logic.
* @param {*} json
* @test { '*': [1, {'+': [5,3]}]} is '1 * (5 + 3)'
* @test { '+': [1, 2, { '*': [5, 3] }] } is '1 + 2 + 5 * 3'
* @test { '-': 1 } is '-1'
* @test { '-': [1,2] } is '1 - 2'
* @test { abs: { '/': [{ '+': [{ '*': [-1, { var: 'test' }] }, 2000] }, 20] } }
* is 'abs((-1 * test + 2000) / 20)'
*/
export function logicToExpression (json) {
const levels = {
'+': 1,
'-': 1,
'*': 2,
'%': 2,
'/': 2
}
if (!json) return JSON.stringify(json)
if (typeof json === 'object') {
const key = Object.keys(json)[0]
const level = levels[key]
const values = (Array.isArray(json[key]) ? json[key] : [json[key]]).map(i => {
const result = logicToExpression(i)
if (i && typeof i === 'object' && !Array.isArray(i)) {
if (level > levels[Object.keys(i)[0]]) return `(${result})`
}
return result
})
if (values.length === 1 && key === '-') return `-${values[0]}`
if (/^[+-/*^%]$/.test(key)) return `${values.join(` ${key} `)}`
if (key === 'var') return values[0].substring(1, values[0].length - 1)
return `${key}(${values.map(i => {
if (i.startsWith('(') && i.endsWith(')')) return i.substring(1, i.length - 1)
return i
}).join(', ')})`
}
return JSON.stringify(json)
}
Thanks a ton, this saved me so much time from having to write a custom traverser to extract a function! Would it be possible to extend this function about to replace variables with optional passed in values (as seen below). It wouldn't be too hard for me to write a secondary function that could do this but wondering if it non-trivial for you to add?
const logic = { abs: { '/': [{ '+': [{ '*': [-1, { var: 'test' }] }, 2000] }, 20] } }
console.log(logicToExpression(logic)) // abs((-1 * test + 2000) / 20)
console.log(logicToExpression(logic, { test: 20 } })) // abs((-1 * 20 + 2000) / 20)
console.log(logicToExpression(logic, { test: '(raw->'property')::numeric' } })) // abs((-1 * (raw->'property')::numeric + 2000) / 20)
Thanks again for this btw, satisfies our use case, myself (and my team) are excited to see the future of this library!
You can modify the if block that overrides how variables are serialized to the expression.
/**
* This method takes JSON Logic and converts it to a reasonable expression that
* represents the logic.
* @param {*} json
* @test { '*': [1, {'+': [5,3]}]} is '1 * (5 + 3)'
* @test { '+': [1, 2, { '*': [5, 3] }] } is '1 + 2 + 5 * 3'
* @test { '-': 1 } is '-1'
* @test { '-': [1,2] } is '1 - 2'
* @test { abs: { '/': [{ '+': [{ '*': [-1, { var: 'test' }] }, 2000] }, 20] } }
* is 'abs((-1 * test + 2000) / 20)'
* @test { abs: { '/': [{ '+': [{ '*': [-1, { var: 'test' }] }, 2000] }, 20] } }, { test: 50 }
* is 'abs((-1 * 50 + 2000) / 20)'
*/
export function logicToExpression (json, data) {
const levels = {
'+': 1,
'-': 1,
'*': 2,
'%': 2,
'/': 2
}
if (!json) return JSON.stringify(json)
if (typeof json === 'object') {
const key = Object.keys(json)[0]
const level = levels[key]
const values = (Array.isArray(json[key]) ? json[key] : [json[key]]).map(i => {
const result = logicToExpression(i, data)
if (i && typeof i === 'object' && !Array.isArray(i)) {
if (level > levels[Object.keys(i)[0]]) return `(${result})`
}
return result
})
if (values.length === 1 && key === '-') return `-${values[0]}`
if (/^[+-/*^%]$/.test(key)) return `${values.join(` ${key} `)}`
if (key === 'var') {
const variable = values[0].substring(1, values[0].length - 1)
if (data && variable in data) return data[variable]
return variable
}
return `${key}(${values.map(i => {
if (i.startsWith('(') && i.endsWith(')')) return i.substring(1, i.length - 1)
return i
}).join(', ')})`
}
return JSON.stringify(json)
}
This works perfectly, thanks once again!
Hi,
Loving this library, been very helpful for our use case.
Am wondering though if it is possible to get the string output of a function to be run from the JSON logic?
Example: