rivescript-js icon indicating copy to clipboard operation
rivescript-js copied to clipboard

Pass a JSON string/object as args to a sub routine

Open ssc2015 opened this issue 7 years ago • 7 comments

Is there any way to pass in a JSON string as an argument to a subroutine like this:

> + Do Something
> - <call>doSomething {action:"print", path:"/one/two"}</call>
> 

I tried a few ways to parse the args as a JSON object and pass it onto another function but I have been unable to get a JSON object that mirrors the JSON string as passed to the subroutine from the rivescript. Is there another approach I can take to accomplish the same?

ssc2015 avatar Nov 16 '16 04:11 ssc2015

I'd like to know this too. some way to handle extra metadata from scripts would be a great addition.

dcsan avatar Nov 16 '16 16:11 dcsan

This isn't currently supported. You also can't really "try anyway" because rivescript-js was updated a while back to use shell-style arguments to object macros, where a "quoted string of words" is sent into the function as a single array item of the args array, rather than the args being a naive list of words separated by space characters. The shell-style parsing would break JSON objects pretty badly. :wink:

It might be a good idea for a new feature to support in the future though: if the contents of a <call> tag begins with a curly bracket {, it could treat the entire contents as a JSON object with a certain minimal schema (such as the JSON has to name the object macro it wants to be sent to), like

+ do something
- <call>{ "object": "doSomething", "action": "print", "path": "/one/two" }</call>

It would be backwards compatible because current object macro names aren't allowed to contain special symbols like the {, so the processing logic could look for the opening { and try to JSON decode the whole thing and go from there. It would probably be given to the object macro as a new argument named data or something, where you could check data.action === "print" etc., so that the existing args argument wouldn't have to change its format and potentially break existing code.

kirsle avatar Nov 16 '16 20:11 kirsle

In the mean time this workaround might give some limited success: https://play.rivescript.com/s/4ls3BcNRoo

Put the JSON into a user variable (should be fine as long as your JSON contains no closing angle bracket > anywhere within), and parse it from the function.

kirsle avatar Nov 16 '16 20:11 kirsle

@kirsle, I will be happy to help with the implementation of this new feature! We can further simplify the JSON schema where "object" key specifies the subroutine to be called and "params" contains an arbitrary key/value pairs as follows:

+ do something
- <call>{ "object": "doSomething", "params": {"action":"print", "path": "/one/two" } }</call>

Please let me know your thoughts on how I can contribute.

Regarding the workaround, I am not sure if it can handle concurrency since the uservar (tmp) could change between multiple invocation of the same trigger by the same user and could potentially run into one invocation getting the args of another invocation, right?

ssc2015 avatar Nov 16 '16 20:11 ssc2015

Yeah, go ahead and send a pull request for this. :smile:

The high level logic I imagine would go something like,

  1. Search for the <call>(.+?)</call> regexp and put the captured text into a text variable.
  2. See if text starts with a { symbol
    1. If so: JSON.parse() it (in a try/catch block, and give an error if the JSON parse fails)
    2. Validate its format (pull out the macro name and parameters)
    3. Call the macro like, .call(rs, functionName, null, jsonParams)
  3. Otherwise, continue as normal:
    1. Separate the function name from its arguments
    2. Call the macro like, .call(rs, functionName, args, null)

The macro function prototype should be updated to function foo(rs, args, params) where the new params argument is the JSON param object. The args argument would be empty or null or something when the macro was called via JSON.

kirsle avatar Nov 16 '16 21:11 kirsle

would it work to pass the whole thing as a quoted string?

- <call>'{ "object": "doSomething", "params": {"action":"print", "path": "/one/two" } }'</call>

dcsan avatar Nov 17 '16 10:11 dcsan

I have been playing around and tried the following:

  • Passed in a JSON style string using single quotes & parenthesis (see below for an example)
  • The arg parser passed along the args string pretty much as is except one thing: it added an extra comma wherever it found a comma in the original string
  • In my subroutine, I simply replaced single quotes with double quotes, '(' with '{', ')' with '}', and double commas with a single comma.
  • Then I was able to parse the JSON string!

Here is one of the snippets I have tried.

! version = 2.0

+ json test * *
- <call>doSomething ('params':('props':('prop1':'val1', 'level2':('prop2':'<star1>', 'prop3':'<star2>'))))</call>

> object doSomething javascript
  var args2 = args.join('');
  args2 = args2.replace(/'/g, '"');
  args2 = args2.replace(/\(/g, '{');
  args2 = args2.replace(/\)/g, '}');
  args2 = args2.replace(/,,/g, ',');
  var json = JSON.parse(args2);
  return JSON.stringify(json);

< object

This approach does not require a major change and continues to keep the <call>tag sane and simple! What do you think?

ssc2015 avatar Nov 17 '16 22:11 ssc2015