lispquestions
lispquestions

Reputation: 441

LISP: how to properly encode a slash ("/") with cl-json?

I have code that uses the cl-json library to add a line, {"main" : "build/electron.js"} to a package.json file:

(let ((package-json-pathname (merge-pathnames *app-pathname* "package.json")))
  (let
    ((new-json (with-open-file (package-json package-json-pathname :direction :input :if-does-not-exist :error)
                  (let ((decoded-package (json:decode-json package-json)))
                    (let ((main-entry (assoc :main decoded-package)))                      
                      (if (null main-entry)
                        (push '(:main . "build/electron.js") decoded-package)
                        (setf (cdr main-entry) "build/electron.js"))
                      decoded-package)))))
    (with-open-file (package-json package-json-pathname :direction :output :if-exists :supersede)
      (json:encode-json new-json package-json))
  )
)

The code works, but the result has an escaped slash:

"main":"build\/electron.js"

I'm sure this is a simple thing, but no matter which inputs I try -- "//", "/", "#//" -- I still get the escaped slash.

How do I just get a normal slash in my output?

Also, I'm not sure if there's a trivial way for me to get pretty-printed output, or if I need to write a function that does this; right now the output prints the entire package.json file to a single line.

Upvotes: 1

Views: 172

Answers (1)

coredump
coredump

Reputation: 38967

Special characters

The JSON Spec indicates that "Any character may be escaped.", but some of them MUST be escaped: "quotation mark, reverse solidus, and the control characters". The linked section is followed by a grammar that show "solidus" (/) in the list of escaped characters. I don't think it is really important in practice (typically it needs not be escaped), but that may explain why the library escapes this character.

How to avoid escaping

cl-json relies on an internal list of escaped characters named +json-lisp-escaped-chars+, namely:

(defparameter +json-lisp-escaped-chars+
  '((#\" . #\")
    (#\\ . #\\)
    (#\/ . #\/)
    (#\b . #\Backspace)
    (#\f . #\)
    (#\n . #\Newline)
    (#\r . #\Return)
    (#\t . #\Tab)
    (#\u . (4 . 16)))
  "Mapping between JSON String escape sequences and Lisp chars.")

The symbol is not exported, but you can still refer to it externally with ::. You can dynamically rebind the parameter around the code that needs to use a different list of escaped characters; for example, you can do as follows:

(let ((cl-json::+json-lisp-escaped-chars+
     (remove #\/ cl-json::+json-lisp-escaped-chars+ :key #'car)))
  (cl-json:encode-json-plist '("x" "1/5")))

This prints:

{"x":"1/5"}

Upvotes: 3

Related Questions