How do I parse JSON in Racket? - json

I can't seem to figure out the {documentation}, there aren't really any examples of parsing some simple JSON data, so I was wondering if anyone here could give me some examples to get started.

Here's a very simple example:
(require json)
(define x (string->jsexpr "{\"foo\": \"bar\", \"bar\": \"baz\"}"))
(for (((key val) (in-hash x)))
(printf "~a = ~a~%" key val))
Here's how you can use it with a JSON-based API:
(require net/http-client json)
(define-values (status header response)
(http-sendrecv "httpbin.org" "/ip" #:ssl? 'tls))
(define data (read-json response))
(printf "My IP address is ~a~%" (hash-ref data 'origin))
At the OP's request, here's how you can create a JSON value from a structure type:
(require json)
(struct person (first-name last-name age country))
(define (person->jsexpr p)
(hasheq 'first-name (person-first-name p)
'last-name (person-last-name p)
'age (person-age p)
'country (person-country p)))
(define cky (person "Chris" "Jester-Young" 33 "New Zealand"))
(jsexpr->string (person->jsexpr cky))

Related

How to convert a string representing JSON data to a string on the curl notation in Common Lisp?

I am using Steel Bank Common Lisp (SBCL), Emacs, and Slime.
After executing a function, I have:
CL-USER> (my-function my-data)
"{\"key-1\": \"value-1\"}"
I would like to convert the string "{\"key-1\": \"value-1\"}" to a REST request notation on curl, so a string as: key-1&value-1
Examplifying the application, this is going to be the data on curl:
curl --request POST --data "userId=key-1&value-1" https://jsonplaceholder.typicode.com/posts
The solution is:
(ql:quickload :yason)
(defun json-string-to-rest-request (json-string)
(destructuring-bind (a b) (alexandria:hash-table-plist (yason:parse json-string))
(format nil "~a&~a" a b)))
You can apply it by:
(defparameter *json-string* "{\"key-1\": \"value-1\"}")
(defparameter *json-string-1* "{\"KeY-1\": \"value-1\"}")
(json-string-to-rest-request *json-string*)
;; => "key-1&value-1"
(json-string-to-rest-request *json-string-1*)
;; => "KeY-1&value-1"
Before this, I tried:
(ql:quickload :cl-json)
(defun json-string-to-rest (json-string)
(with-input-from-string (s json-string)
(destructuring-bind ((a . b)) (cl-json:decode-json s)
(format nil "~a&~a" (string-downcase a) b))))
(json-string-to-rest *json-string*)
;; => "key-1&value-1"
However, the downside is that cl-json:decode-json transforms the json string first to ((:KEY-1 . "value-1")) - so case sensistivity gets lost in the json key string when using cl-json.
The package yason reads-in the keys as strings and therefore preserves case-sensitivity in the key string.

json-get function in lisp

In these day i'm working to a json parse in prolog and lisp.
yesterday with your help i finished the prolog project and now i need help again.
the funcion is always json-get but now in lisp.
this is the functin that i wrote:
(defun json-get (json_obj fields &optional n)
(let ((place (assoc fields json_obj :test 'string=)))
(if (null place)
n
(ns (second place) t)))
the behavior of the funtion should be the same of the prolog predicate.
for example if the input is:
CL-prompt> (defparameter x (json-parse "{\"nome\" : \"Arthur\",\"cognome\" : \"Dent\"}"))
X
CL-prompt> x
(json-obj ("nome" "Arthur") ("cognome" "Dent"))
the output should be:
CL-prompt> (json-get x "cognome")
"Dent"
insted, if the input is:
(json-get (json-parse
"{\"name\" : \"Zaphod\",
\"heads\" : [[\"Head1\"], [\"Head2\"]]}")
"heads" 1 0)
the output should be:
"Head2"
the function that i wrote is totally wrong?
P.S. for this project are forbidden functions like SET, SETQ, SETF e MULTIPLE-VALUE-SETQ and DO, DO*, DOTIMES, DOLIST e LOOP and DEFPARAMETER, DEFVAR e DEFCOSTANT inside a function
thanks guys
edit 1:
this is the description of this funcion,
a json-get function that accepts a JSON object
(represented in Common Lisp, as produced by the json_parse function) and a series of
"Fields", retrieve the corresponding object. A field represented by N (with N a number
greater than or equal to 0) represents an index of a JSON array.
edit 2 :
if i try to run json-get lisp answer me with:
Error: The variable PLACE is unbound.
You need to implement this recursively. You also need to distinguish JSON arrays (which are implemented as a list of elements prefixed with json-array) and JSON objects (which are implemented as an association list.
(defun json-get (json_obj fields)
(if (null fields) ; base case of recursion
json_obj
(let* ((cur-key (car fields))
(current (cond ((and (integerp cur-key)
(eq (car json_obj) 'json-array))
(nth (1+ cur-key) json_obj)) ; add 1 to skip over JSON-ARRAY
((and (stringp cur-key)
(eq (car json_obj) 'json-obj))
(second (assoc cur-key (cdr json_obj) :test #'string=))) ; Use CDR to skip over JSON-OBJ
(t (error "~S is not a JSON object or array or ~s is not appropriate key" json_obj cur-key)))))
(json-get current (cdr fields)))))
fields has to be a list of fields, so your second example would be:
(json-get (json-parse
"{\"name\" : \"Zaphod\",
\"heads\" : [[\"Head1\"], [\"Head2\"]]}")
'("heads" 1 0))
and the first example should be:
(json-get x '("cognome"))

write json object in lisp

Good morning i need help to write this function in lisp.
this is the description.
The json-write function writes the JSON object to the filename file in JSON syntax. If
filename does not exist, it is created and if it exists it is overwritten. Of course it is expected that
CL-PROMPT> (json-load (json-write '(json-obj # | stuff | #) "foo.json"))
(json-obj # | stuff | #)
and this is my function but it isn't correct
(defun json-write (json filename)
(with-open-file (out filename
:direction :output
:if-exists :overwrite
:if-does-not-exist :create)
(pprint (json out))))
thanks
Edit 1:
i try to write var json (that is a json object) into filename.
but pprint doesn't write anything to filename
Edit 2:
(defun json-write (json filename)
(with-open-file (out filename
:direction :output
:if-exists :supersede
:if-does-not-exist :create)
(cond
((equal (first json) ‘json-array) (write-arr json))
((equal (first json) ‘json-obj) (write-obj json)))))
so i try this now
if json is json-array lisp call write-arr json
if json is json-obj call write-obj
so my idea is that write-arr trasform
(json-array 1 2 3) in `"[1, 2, 3]"` to make it parsable
and write-obj trasform
(json-obj ("nome" "Arthur") ("cognome" "Dent"))
in
"{\"nome\" : \"Arthur\",
\"cognome\" : \"Dent\"}"
and then write everythings into filename with format stream.
how can i format (json-array 1 2 3) in "[1, 2, 3]".
with format function? and then calling recursively even this function?
thank you
When you write (pprint (json out)) you are trying to call a global function called json. I think you mean (pprint json out).
Now here is roughly how to write a simple json printer:
(defun write-json (object stream)
(etypecase object
(number (format stream “~a” object))
(string (print object stream))
(null (format stream “null”))
(cons
(ecase (car object)
(json-array
(write-char #\[ stream)
(loop for (x . r) on (cdr object)
do (write-json x stream)
(when r (write-char #\, stream)))
(write-char #\] stream))))))
There are plenty of gaps to fill in. And note that this will print strings wrong if they contain new lines. And you probably want to do something less stupid for printing numbers.
Here is how I would implement a json pretty printer with CLOS:
(defvar *json-pretty* nil)
(defvar *json-indent-level* 0)
(defvar *json-indent-spaces* 2)
(defun json-fresh-line (stream)
(if *json-pretty*
(progn
(fresh-line stream)
(loop repeat (* *json-indent-level* *json-indent-spaces*)
do (write-char #\Space stream)))
(write-char #\Space stream))
(defgeneric write-json (object stream))
(defgeneric write-json-collection (type data stream))
(defmethod write-json ((object cons) stream)
(write-json-collection (car object) (cdr object) stream))
(defmethod write-json-collection ((tag (eql json-array)) data stream)
...)
There are plenty of methods left to write.

REST API JSON Parsing in Racket

I'm developing rest service in Racket in educational purposes and facing problem with JSON parsing.
I have POST request with following body
"{\"word\": \"a\", \"desc\": \"b\"}"
Also I have request handler for this request such as
(define (add-word-req req)
(define post-data (request-post-data/raw req))
(display post-data)
(newline)
(define post-data-expr1 (bytes->jsexpr post-data))
(display post-data-expr1)
(newline)
(display (jsexpr? post-data-expr1))
(display (hash? post-data-expr1))
(newline)
(define post-data-expr (string->jsexpr "{\"word\": \"a\", \"desc\": \"b\"}"))
(display post-data-expr)
(newline)
(display (hash? post-data-expr))
(newline)
(for (((key val) (in-hash post-data-expr)))
(printf "~a = ~a~%" key val))
(cond
[(jsexpr? post-data-expr)
;; some conditional work
...
]
[else
...]
)
)
As you can see, request body and hard-coded JSON are same.
While processing request I get next output:
"{\"word\": \"a\", \"desc\": \"b\"}"
{"word": "a", "desc": "b"}
#t#f
#hasheq((desc . b) (word . a))
#t
desc = b
word = a
Body is transmitted in one piece and even transformed into jsexpr, but still it is not hash! And because of it I can't use hash methods for post-data-expr1.
What is the best way to get hash from such jsexpr? Or should I use another method to obtain key/values?
Regards.
This program:
#lang racket
(require json)
(string->jsexpr "{\"word\": \"a\", \"desc\": \"b\"}")
(bytes->jsexpr #"{\"word\": \"a\", \"desc\": \"b\"}")
has the output:
'#hasheq((desc . "b") (word . "a"))
'#hasheq((desc . "b") (word . "a"))
This means that bytes->jsexpr ought to work.
Can you change:
(define post-data (request-post-data/raw req))
(display post-data)
(newline)
to
(define post-data (request-post-data/raw req))
(write post-data)
(newline)
?
I have a feeling that the contents of the byte string is slightly different than the one you use later on.
Note:
> (displayln "{\\\"word\\\": \\\"a\\\", \\\"desc\\\": \\\"b\\\"}")
{\"word\": \"a\", \"desc\": \"b\"}
> (displayln "{\"word\": \"a\", \"desc\": \"b\"}")
{"word": "a", "desc": "b"}
The output of your (display post-data-expr1) matches the first interaction above. My guess is therefore that both backslashes and quotes are escaped in your input.

How to output false while using cl-json

In Common Lisp, I'm using cl-json to output json format, but how can I output a false instead of null?
This is the set of utilities I use when I need to properly handle false with cl-json:
(defclass json-false ()
())
(defmethod json:encode-json ((object json-false) &optional stream)
(princ "false" stream)
nil)
(defvar *json-false* (make-instance 'json-false))
(defun json-bool (val)
(if val t *json-false*))
(defun json-bool-handler (token)
(or (string= token "true")
(and (string= token "false") *json-false*)))
(defmacro preserving-json-boolean (opts &body body)
(declare (ignore opts))
`(let ((json:*boolean-handler* #'json-bool-handler))
,#body))
Now, to encode a literal false, I would way
* (json:encode-json-to-string `((foo . nil) (bar . t) (baz . ,*json-false*)))
"{\"foo\":null,\"bar\":true,\"baz\":false}"
Or, to encode a LISP boolean into a json boolean:
* (let ((something nil))
(json:encode-json-to-string `((bool . ,(json-bool something)))))
"{\"bool\":false}"
Or, to read in JSON data preserving the distinction between null and false:
* (preserving-json-boolean ()
(json:decode-json-from-string "{\"foo\":null,\"bar\":true,\"baz\":false}"))
((:FOO) (:BAR . T) (:BAZ . #<JSON-FALSE #x21029D2E4D>))
Of course, some care must be taken when reading in data like that;
* (when (cdr (assoc :baz *))
'yep)
YEP