What is the best way to interpret and execute conditions specified within strings like "condition1 and condition2"?

Received a JSON file from an external source with various conditions that need to be tested, either in real-time or through conversion.

Imagine having an instance of a class called Person, with attributes

{age: 13, country: "Norway"}
, and an external JSON file with the following "helpers":

{
    "is_child": "age < 16",
    "is_adult": "age >= 16 and age < 67",
    "is_senior": "age > 67",
    "is_scandinavian": "country == 'Norway' or country == 'Sweden' or country == 'Denmark'",
}

and another file containing tickets like "NorwegianTickets.json":

{
    "childTicket": "is_child and is_scandinavian",
    "flexTicket": "is_scandinavian and (is_adult or is_senior)"
}

Need guidance on implementing this logic in code. For example, when applying the "flexTicket" condition to a "Person" object, how should the logic be mapped? How to interpret the "stringed" conditions like "and"/"or" and "()"?

Answer №1

To accomplish this task effortlessly, you can utilize the eval function, which executes a string as javascript.

Here is the basic logic:

  1. Create the different conditions as a javascript string (is_child, is_adult, ...)

This function will replace all variables (represented as strings) with their corresponding values. To achieve this, you will need to build a dictionary listing all variables along with their values:

const varsToReplace = {
  country: 'Norway',
  age: 12
}

Next, replace these variables in a given condition using the replace method. A crucial point here is to search for

‎‎‏‏‎ ‎‎country‏‏‎ ‎
instead of just country (the extra space before and after prevents unintended replacements like user_country becoming ‏‏‎user_Norway). Additionally, remember to enclose string replacements in '':

const getConditionString = (condition) => {
  let replacedCondition = ` ${conditions[condition]} `

  Object.keys(varsToReplace).forEach((variable) => {
    const re = new RegExp(` ${variable} `, 'g');

    let replaceValue = ` ${varsToReplace[variable]} `

    // Wrap string values in ''
    if (typeof varsToReplace[variable] === 'string') {
      replaceValue = ` '${varsToReplace[variable]}' `
    }

    replacedCondition = replacedCondition.replace(re, replaceValue)
  })

  return replacedCondition
}

  1. Create the test as a javascript string (is_child and is_scandinavian, ...)

The getTestString function replaces all condition keys with the corresponding javascript strings using the previous function:

const getTestString = (test) => {
  let replacedTest = ` ${tests[test]} `

  Object.keys(conditions).forEach((condition) => {
    const re = new RegExp(` ${condition} `, 'g');

    replacedTest = replacedTest.replace(re, ` ( ${getConditionString(condition)} ) `)
  })

  return replacedTest
}

  1. Convert the various operators to be 'js valid':
const replaceOperators = (string) => {
  const operators = {
    or: '||',
    and: '&&'
  }

  Object.keys(operators).forEach((operator) => {
    const re = new RegExp(` ${operator} `, 'g');

    string = string.replace(re, ` ${operators[operator]} `)
  })

  return string
}

  1. Execute the javascript string using eval:
const evalTest = (test) => {
  let testAsString = replaceOperators(getTestString(test))

  return eval(testAsString)
}

Here is the full example:

const country = 'Norway'
const age = 12

const varsToReplace = {
  country,
  age
}

const conditions = {
  "is_child": "age < 16",
  "is_adult": "age >= 16 and age < 67",
  "is_senior": "age > 67",
  "is_scandinavian": "country == 'Norway' or country == 'Sweden' or country == 'Denmark'"
}

const tests = {
  "childTicket": "is_child and is_scandinavian",
  "flexTicket": "is_scandinavian and ( is_adult or is_senior )"
}


const getConditionString = (condition) => {
  let replacedCondition = ` ${conditions[condition]} `

  Object.keys(varsToReplace).forEach((variable) => {
    const re = new RegExp(` ${variable} `, 'g');

    let replaceValue = ` ${varsToReplace[variable]} `

    if (typeof varsToReplace[variable] === 'string') {
      replaceValue = ` '${varsToReplace[variable]}' `
    }

    replacedCondition = replacedCondition.replace(re, replaceValue)
  })

  return replacedCondition
}

const getTestString = (test) => {
  let replacedTest = ` ${tests[test]} `

  Object.keys(conditions).forEach((condition) => {
    const re = new RegExp(` ${condition} `, 'g');

    replacedTest = replacedTest.replace(re, ` ( ${getConditionString(condition)} ) `)
  })

  return replacedTest
}

const replaceOperators = (string) => {
  const operators = {
    or: '||',
    and: '&&'
  }

  Object.keys(operators).forEach((operator) => {
    const re = new RegExp(` ${operator} `, 'g');

    string = string.replace(re, ` ${operators[operator]} `)
  })

  return string
}

const evalTest = (test) => {
  let testAsString = replaceOperators(getTestString(test))

  console.log(testAsString)

  return eval(testAsString)
}

console.log(evalTest('childTicket'))
console.log(evalTest('flexTicket'))

Answer №2

Creating a DSL for this purpose sounds like a fun idea. I've actually developed one to provide you with a glimpse of how it works. However, it's important to note that the DSL is not fully tested and lacks some basic functionalities like array access. You may be able to find more robust examples online.

class Node_ {
    children: Node_[];
    
    constructor() {
        this.children = [];
    }

    addChild = (node: Node_) =>
        this.children.push(node);

    evaluate = (context: any): boolean | number | string => {
        throw new Error('Missing implementation');
    }
}

enum ExprType {
    Eq = 'eq',
    Gt = 'gt',
    Lt = 'lt',
    Gte = 'gte',
    Lte = 'lte',
    Get = 'get',
}

class ExprNode extends Node_ {
    ...
    ...
    ...
}

class ValueNode extends Node_ {
    ...
    ...
    ...
}

function tokenize(value: string): Node_ {
    ...
    ...
    ...
}

function consumeString(value: string, index: number) {
    ...
    ...
    ...
}

function addToParent(nodeStack: Node_[]) {
    ...
    ...
    ...
}

{
    ...
    ...
    ...
}

{
    ...
    ...
    ...
}

{
    ...
    ...
    ...
}

{
    ...
    ...
    ...
}

{
    ...
    ...
    ...
}

{
    ...
    ...
    ...
}

{
    ...
    ...
    ...
}

I didn't include AND and OR expressions in this implementation, but it should be straightforward to add them if needed.

In this setup, the client is expected to provide data along with constraints. For instance:

{
    "context": {
        "person": {
            "age": 44,
            "planet": "saturn"
        }
    },
    "constraints": {
        "shouldFrom": "EQ('mars', GET('person', GET('planet')))",
        "minimumAge": "GTE(40, GET('person', GET('planet')))"
    }
}

On the receiver end, each constraint is parsed, tokenized, and evaluated against the provided context.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

In a standalone script, the error message "ReferenceError: exports is not defined in ES module scope" is encountered

When I execute the script using npx ts-node -i --esm --skipProject -T .\seed.ts import { readdir, readFile } from "node:fs/promises" async function readFeedsFromFiles() { const data = await readdir("./seedData/feeds", { ...

How to include a javascript file in a vuejs2 project

Just starting out with the Vue.js framework and I've hit a snag trying to integrate js libraries into my project. Would greatly appreciate any assistance! By the way, I attempted adding the following code to my main.js file but it didn't have th ...

Struggling with integrating Meteor.wrapAsync with the mailchimp-api-v3

I am currently exploring the use of mailchimp-api-v3 in a Meteor project (1.4.1.3) and I particularly like the batch support it offers. To make the call, I have enclosed it within Meteor's .wrapAsync function (which had a bit of a learning curve, but ...

Difficulties arising when trying to convert latitude and longitude coordinates to xyz for camera rotation within Three.js

Currently, I am working on a JavaScript application that allows users to design their own closet. My goal is to enable smooth rotation of the closet without changing the distance from the camera to the closet. While it would be simple to rotate the object ...

A guide to implementing a For-Each Loop on Argument Array within Functions using Java Script

My code is not functioning properly. I am trying to calculate the sum of numbers provided by the user as arguments. I have attempted to use the Argument Object, but I can't seem to figure out what mistake I've made. // The Argument Object funct ...

Outdated jQuery script no longer functioning (Wordpress)

I recently updated a WordPress site to Version 5.7.2 and now two of the custom Metaboxes are not functioning as expected. The issue seems to be related to the outdated jQuery version used by the Metaboxes. To address this problem, I installed a Plugin cal ...

Select characteristics with designated attribute types

Is there a way to create a type that selects only properties from an object whose values match a specific type? For example: type PickOfValue<T, V extends T[keyof T]> = { [P in keyof (key-picking magic?)]: T[P]; }; I am looking for a solution w ...

Implementing a click event listener on an iframe that has been dynamically generated within another iframe

Below is the code I used to attach a click event to an iframe: $("#myframe").load(function() { $(this.contentWindow.document).on('click', function() { alert("It's working properly"); }); }) Everything seems to be working co ...

bespoke session with Next.js using Next-Auth

I encountered an issue while migrating my JS file to TSX. What I am trying to do is sign in with credentials and customize the session user to my user data. // api/auth/[...nextauth].js import NextAuth from "next-auth"; import Providers from &qu ...

Can spreading be used for destructuring?

These were the initial props I attempted to pass to a component: const allprops = { mainprops:{mainprops}, // object pageid:{pageId}, // variable setpageid:{setPageId}, // state function makerefresh:{makeRefresh} // state function } <Na ...

The Angular ResolveFn error states that the inject() function must be invoked within an injection context

As I attempted to phase out Angular's "Resolve" class implementation in favor of the "ResolveFn" functional implementation, I encountered a perplexing issue. I have a basic resolver that is preparing my data. I am facing an error that has left me puzz ...

Iterate through the object received from the node promise and pass it to the subsequent .then method

After grappling with this issue for what feels like an eternity, I find myself immersed in learning about Node.js and trying to grasp the concept of promises. My current challenge involves retrieving data from the Spotify API, starting with fetching my ow ...

The ajax function does not provide a response

Can you help me figure out why this JavaScript function keeps returning 'undefined'? I really need it to return either true or false. I've included my code below: function Ajax() { var XML; if(window.XMLHttpRequest) XML=new ...

Concealing information based on the value of a variable

Creating a dynamic price estimator. I need help writing a jQuery function that can hide or show a specific div element based on the value of a variable. For example, let's say I have an HTML div with the ID 'Answer': <div id="answer"&g ...

Obtain an item from an array by utilizing the property with Lodash _.find

I currently have an array of objects called memberToChange.checkboxes: ICheckbox[] structured like this: https://i.stack.imgur.com/LyKVv.png Within this setup, there is also a variable named internalNumber with the value "3419". My objective is to locate ...

Vue 3 - Using Emit Functionality in a Reusable and Composable File

I'm trying to utilize the emit function in my file called useGoo.ts import Swal from "sweetalert2/dist/sweetalert2.js"; export default function useModal() { const { emit } = getCurrentInstance(); function myId() { emit('id&ap ...

Mutex in node.js(javascript) for controlling system-wide resources

Is there a way to implement a System wide mutex in JavaScript that goes beyond the usual mutex concept? I am dealing with multiple instances of a node.js cmd running simultaneously. These instances are accessing the same file for reading and writing, and ...

What is the best way to iterate over JSON data from an endpoint that contains multiple nested arrays using the .map() method?

Seeking to showcase weather API data from: () import Image from "next/image" interface Hour { time_epoch: number time: string temp_c: number temp_f: number is_day: number wind_mph: number wind_kph: number wind_deg ...

Attempting to provide varying values causes If/Else to become unresponsive

I've implemented a function that scans a file for a specific term and then outputs the entire line as a JSON object. Strangely, when I include an else statement in the logic, an error occurs: _http_outgoing.js:335 throw new Error('Can\& ...

Navigating through Next.js for slug URLs such as site.com/username

Can someone help me figure out how to create a profile page for each username, like site.com/jack or site.com/jill? I'll be pulling the username info from an API that also contains the user ID and email. I'm new to Next.js and would really appre ...