camunda/feel-scala

Stringify JSON objects

aleksander-dytko opened this issue · 11 comments

Is your feature request related to a problem? Please describe.
During a conversation with a customer, they mentioned that it would be great to be able to structuralize JSON object with FEEL expression to stringify it.

Describe the solution you'd like
A new built-in function to return a JSON string of a FEEL value, like JSON.stringify().

// Function signature
to json(value: Any): String

// Examples
// 1) FEEL context to JSON object
to json({a: 1, b: 2})
// "{\"a\": 1, \"b\": 2}"

// 2) FEEL value to JSON literal
to json(1)
// "1"

to json("a")
// "\"a\""

to json(true)
// "true"

to json(null)
// "null"

// 3) FEEL list to JSON array
to json([1, 2, 3])
// "[1, 2, 3]"

// 4) FEEL date/time/date-time to JSON string
to json(@"2023-06-14")
// "\"2023-06-14\"" 

to json(@"14:55:00")
// "\"14:55:00\"" 

to json(@"2023-06-14T14:55:00")
// "\"2023-06-14T14:55:00\"" 

// 5) FEEL duration to JSON string 
to json(@"P1Y")
// "\"P1Y\"" 

to json(@"PT2H")
// "\"PT2H\"" 

// 6) FEEL function to JSON string 
to json(function (a, b) a + b)
// "\"<function>\"" 

// 7) FEEL range to JSON string 
to json([1..10])
// "\"[1..10]\"" 

Related issues

EDIT: I updated the description to focus on producing a JSON string and to propose a new built-in function with examples.

saig0 commented

@aleksander-dytko please provide more input for this issue.

What is the actual use case?
Do you have an example?
In which context is the expression evaluated? BPMN@Zeebe?

What should the function do?

In Zeebe, all variables are stored as JSON objects. In FEEL, we can access these JSON objects naturally as FEEL context objects.

The result of a FEEL expression is always a FEEL type, for example, a FEEL context.

vobu commented

@aleksander-dytko please provide more input for this issue.

What is the actual use case? Do you have an example? In which context is the expression evaluated? BPMN@Zeebe?

What should the function do?

given I talked to @aleksander-dytko about this, I should chime in here: in a BPMN task, we needed to convert a string into an object to subsequently access its' properties. This all in FEEL.

task1: job worker executes and produces a stringified representation of a JSON object (foo = '{ some:"value" }' that is sent back to the task1 and thus available in the "global" variable scope

task2: gets foo, and wants to access foo.some → prerequisite for this would be to have a way to convert the stringified JSON object into an actual runtime object. In JS, this is provided by the JSON class.
So we'd do const anotherFoo = JSON.parse(foo) to be able to subsequently do anotherFoo.some.
And to have an equivalent in FEEL would be excellent.

McAlm commented

@aleksander-dytko: Another use case would be if a JSON string (coming from some database) is being passed to a DMN decision, e.g.

    {
        "WRAPPER": [
          {
            "jsonPayLoadAsString": "\"questions\": [{ \"type\": \"someType\" },{ \"role\": \"VN\" },{\"comment\": \"some comments\"}]\"",
            "marker": "N"
          }
        ]
    }

We are interested to evaluate some DMN rules on the jsonPayloadAsString values, so we need to transform it into a JSON object first.

saig0 commented

@vobu / @McAlm Thank you for providing your input. 👍

It seems that we need a new built-in function to parse a stringified JSON into a FEEL context/value.

Do we need the parsing only for JSON documents (i.e. "{..}")? Or, any kind of JSON value (i.e. "sting", 1, true)?


Note that this is different from the original requirement:

it would be great to be able to structuralize JSON object with FEEL expression to stringify it.

Hi, I also have a use case wher I'm calling the OpenAI REST API and ask to return a proper JSON string as a response. Then I need to turn that into a FEEL context. I think a new built-in FEEL function would be good.
I also created a forum entry on this : https://forum.camunda.io/t/parse-a-json-string-to-feel-context/44755

saig0 commented

Based on the previous comments, I suggest the following new built-in functions:

Signature

Parses a string into a FEEL value:

from json(json: string): Any

Serializes a FEEL value into a JSON string:

to json(value: Any): String

Examples

// 1) JSON object to FEEL context
from json("{\"a\": 1, \"b\": 2}")
// {a: 1, b: 2}

// 2) JSON literal to FEEL value
from json("1")
// 1

from json("\"a\"")
// "a"

from json("true")
// true

from json("null")
// null

// 3) JSON array to FEEL list
from json("[1, 2, 3]")
// [1, 2, 3]

// 4) JSON string of a date/time/date-time to FEEL string 
from json("\"2023-06-14\"")
// "2023-06-14" 

from json("\"14:55:00\"")
// "14:55:00" 

from json("\"2023-06-14T14:55:00\"")
// "2023-06-14T14:55:00" 

// 5) JSON string of a duration to FEEL string 
from json("\"P1Y\"")
// "P1Y" 

from json("\"PT2H\"")
// "PT2H" 

// 6) invalid JSON to FEEL null 
from json("invalid")
// null 
// 1) FEEL context to JSON object
to json({a: 1, b: 2})
// "{\"a\": 1, \"b\": 2}"

// 2) FEEL value to JSON literal
to json(1)
// "1"

to json("a")
// "\"a\""

to json(true)
// "true"

to json(null)
// "null"

// 3) FEEL list to JSON array
to json([1, 2, 3])
// "[1, 2, 3]"

// 4) FEEL date/time/date-time to JSON string
to json(@"2023-06-14")
// "\"2023-06-14\"" 

to json(@"14:55:00")
// "\"14:55:00\"" 

to json(@"2023-06-14T14:55:00")
// "\"2023-06-14T14:55:00\"" 

// 5) FEEL duration to JSON string 
to json(@"P1Y")
// "\"P1Y\"" 

to json(@"PT2H")
// "\"PT2H\"" 

// 6) FEEL function to JSON string 
to json(function (a, b) a + b)
// "\"<function>\"" 

// 7) FEEL range to JSON string 
to json([1..10])
// "\"[1..10]\"" 

In the OMG issue, there is an interesting comment about time/date and time values with a timezone id:

[A FEEL date/time/date and time returns as] A string representation conforming to an ISO 8601 Date, Time or Date and Time combination.
If the FEEL date and time contains an IANA timezone id, the ISO 8601 Date and Time is suffixed by the IANA timezone id in rectangular brackets,
e.g. 2007-12-03T10:15:30+01:00[Europe/Paris]


@vobu / @McAlm / @vincentgiraud would this suggestion work for you?

Hi @saig0 , that would work indeed. Thanks.

@saig0 Have you considered using the string(from) conversion function for this? Currently, it returns null for any context.

saig0 commented

Have you considered using the string(from) conversion function for this?

@korthout good hint. But, I think that the string() function is not sufficient to produce a correct JSON representation in all cases, for example, for context and temporal types.

In this TCK test case, a context is returned as "{a: \"foo\"}" vs. "{\"a\": \"foo\"}".

Currently, it returns null for any context.

Yes. There is an open issue here: #650

I fixed this issue in the version 1.17.0.

I updated the issue description to focus on producing a JSON string of a FEEL value. I moved the parsing part into the issue #825.