Question

I have another question regarding decoding JSON in Common Lisp. I settled on ST-JSON as my tool. I am able to get a JSO object containing the JSON data, and access all fields with st-json:getjso. I wanted to write a macro similar in principle to destructuring-bind that would provide local bindings to variables named after JSON fields (since then I started doubting if this is a good idea, but that's a different question). I came up with the following:

(defmacro destructure-jso (jso &body body)
  (let (keys values)
    (st-json:mapjso #'(lambda (key value)
            (push key keys)
            (push value values))
            jso)
    `(destructuring-bind ,keys ,values
       ,@body)))

But when I try to use it on a JSO object, I get the error The value PARAMS is not of the expected type STRUCTURE. where PARAMS is the object. Can someone explain this to me?

Thanks.

Was it helpful?

Solution

Apparently, you are using destructure-jso like this:

(let ((params (st-json:read-json-from-string "{\"foo\":42,\"bar\":\"baz\"}")))
  (destructure-jso params
    (list foo bar)))

However, destructure-jso, being a macro, gets handled at macro expansion time, much before the JSON even gets parsed. params is passed to your macro as a symbol, without being evaluated; and even if its evaluation was attempted, it would be unbound.

So, if you want to write a destructure-jso, you will need the list of keys at macro expansion time. You could pass the list in a normal way:

> (defmacro destructure-jso-2 (vars json &body body)
    `(let ,(mapcar #'(lambda (var)
                       (list var `(getjso ,(string-downcase (symbol-name var)) ,json)))
                   vars)
       ,@body))
DESTRUCTURE-JSO-2

> (let ((params (st-json:read-json-from-string "{\"foo\":42,\"bar\":\"baz\"}")))
    (destructure-jso-2 (foo bar)
    params
  (list foo bar)))
(42 "baz")

Or, if you like, use a "template" JSON for creating the mappings:

> (defmacro destructure-jso-3 (template json &body body)
    (let (bindings)
      (st-json:mapjso #'(lambda (key val)
                          (declare (ignore val))
                          (push (list (intern (string-upcase key)) `(getjso ,key ,json))
                                bindings))
                      (st-json:read-json-from-string template))
      `(let ,bindings
         ,@body)))
DESTRUCTURE-JSO-3

> (let ((params (st-json:read-json-from-string "{\"foo\":42,\"bar\":\"baz\"}")))
    (destructure-jso-3 "{\"foo\":null,\"bar\":null}"
        params
      (list foo bar)))
(42 "baz")

Here, the variable bindings come from the first (template) JSON, values from the second one. The template JSON is parsed at macroexpansion time, the params JSON every time your code is executed.

Whether either of these is a useful approach for you or not, I do not know.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top