Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merging two JSONata expressions

I am using JSONata for performing JSON to JSON transformation.

For some unique reasons, I want to merge two JSONata expressions :

As an example :

Parent Expression:

var script = `
{
      "data":
      {
          "name" : data.payload.Name.(FirstName & ' ' & LastName),    
          "alias": data.payload.Name.(Salutation & ' ' & FirstName),
          "active": data.payload.Status = 'New' ? true : false,
          "signature": "Have good day ," & data.payload.Name.FirstName & "!"
       }       
  }
`;

Also I have few simple assignment kind of JSONata expression like :

Expression 1 :

{
   "source" : source
}

Expression 2 :

{
  "data": {
     "email" : data.payload.Email
   }
}

I would like to add above two expressions to expressions defined using script.

So after adding these two expressions, I should be able to get :

var script = `
{
      "source": source,
      "data":
      {
          "name" : data.payload.Name.(FirstName & ' ' & LastName),    
          "alias": data.payload.Name.(Salutation & ' ' & FirstName),
          "active": data.payload.Status = 'New' ? true : false,
          "signature": "Have good day ," & data.payload.Name.FirstName & "!",
          "email": data.payload.Email
       }       
  }
`;

How do I do using javascript/JSONata ?

Background and constraints :

  1. Child Expressions (expression 1 and 2 in the example) (that is supposed to be added into Parent expression) will always be simple assignment like "a" : x.y.z or "b" : x.

  2. Child Expressions may already be present in parent expression. In that case, it replaces assignment.

  3. Also I want to delete some json paths from parent expression (ofcouse , if it is present) like If delete path data.Email.

What I have done ? :

  • I tried to convert JSONata script to JSON by putting values under double quotes and encoding value using escape() function.
  • Once I have JSON, I look for path mentioned in child expression (like data.Email)

    • If path exists : replace its value
    • If path does not exist : create path and assign value
    • If path is supposed to be deleted : simply delete it.
  • Once I have done processing above JSON, I convert it to JSONata script by removing quotes using bunch of regex and then applying unescape() method for decoding.

  • The problem with this approach is :

    • It is not reliable (regex matching and replacement is not fullproof)
    • I am not sure whether every JSONata (which does not declare any functions) can be converted to valid JSON always.
like image 977
Utsav Chokshi Avatar asked Oct 28 '22 08:10

Utsav Chokshi


1 Answers

I think that your best bet might be to translate your expressions to the JSONata AST and then merge them into a new AST.

Here's a super simple example:

const ast1 = jsonata(expr1).ast();
const ast2 = jsonata(expr1).ast();

if (ast1.type !== "unary" || ast2.type!== "unary") throw Error("Only support unary expressions")

const combinedAst = {
  "type": "unary",
  "value": "{",
  "lhs": [...ast1.lhs, ast2.lhs]   
 }

 // TODO: Serialize the AST or inject it into jsonata() 

The problem is what to do with your new AST. In my case I also wrote a custom serializer to turn the AST back into a JSONata string, and evaluate that.


ASTs in use

AST for Expression 1

{
  "type": "unary",
  "value": "{",
  "position": 1,
  "lhs": [
    [
      {
        "value": "source",
        "type": "string",
        "position": 13
      },
      {
        "type": "path",
        "steps": [
          {
            "value": "source",
            "type": "name",
            "position": 22
          }
        ]
      }
    ]
  ]
}

AST for Expression 2

{
  "type": "unary",
  "value": "{",
  "position": 1,
  "lhs": [
    [
      {
        "value": "data",
        "type": "string",
        "position": 10
      },
      {
        "type": "unary",
        "value": "{",
        "position": 13,
        "lhs": [
          [
            {
              "value": "email",
              "type": "string",
              "position": 26
            },
            {
              "type": "path",
              "steps": [
                {
                  "value": "data",
                  "type": "name",
                  "position": 33
                },
                {
                  "value": "payload",
                  "type": "name",
                  "position": 41
                },
                {
                  "value": "Email",
                  "type": "name",
                  "position": 47
                }
              ]
            }
          ]
        ]
      }
    ]
  ]
}

Combined AST

{
  "type": "unary",
  "value": "{",
  "position": 1,
  "lhs": [
    [
      {
        "value": "source",
        "type": "string",
        "position": 12
      },
      {
        "type": "path",
        "steps": [
          {
            "value": "source",
            "type": "name",
            "position": 20
          }
        ]
      }
    ],
    [
      {
        "value": "data",
        "type": "string",
        "position": 30
      },
      {
        "type": "unary",
        "value": "{",
        "position": 33,
        "lhs": [
          [
            {
              "value": "email",
              "type": "string",
              "position": 46
            },
            {
              "type": "path",
              "steps": [
                {
                  "value": "data",
                  "type": "name",
                  "position": 53
                },
                {
                  "value": "payload",
                  "type": "name",
                  "position": 61
                },
                {
                  "value": "Email",
                  "type": "name",
                  "position": 67
                }
              ]
            }
          ]
        ]
      }
    ]
  ]
}
like image 169
logan Avatar answered Oct 31 '22 10:10

logan