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.
Related
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.
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"))
What is the fastest way to read a csv file in CL in a way such that:
1) all fields in the first line go into one array called column-names
2) the first field of each of all following lines goes into another
array called row-names
3) all other fields go into another array called values
?
My file has the following form, just with a lot more columns and rows:
"";"ES1 Index";"VG1 Index";"TY1 Comdty";"RX1 Comdty";"GC1 Comdty"
"1999-01-04";1391.12;3034.53;66.515625;86.2;441.39
"1999-01-05";1404.86;3072.41;66.3125;86.17;440.63
"1999-01-06";1435.12;3156.59;66.4375;86.32;441.7
"1999-01-07";1432.32;3106.08;66.25;86.22;447.67
And the result I would like is:
#("1999-01-04" "1999-01-05" "1999-01-06" "1999-01-07" )
#("" "ES1 Index" "VG1 Index" "TY1 Comdty" "RX1 Comdty" "GC1 Comdty")
#(1391.12 3034.53 66.515625 86.2 441.39 1404.86 3072.41 66.3125 86.17 440.63
1435.12 3156.59 66.4375 86.32 441.7 1432.32 3106.08 66.25 86.22 447.67)
Are you aware of some CL library that does so already?
Are there any general issues regarding I/O performance, maybe compiler-specific, that I should be aware of?
Here is the way I am doing it now:
(with-open-file (stream "my-file.csv" :direction :input)
(let* ((header (read-line stream nil))
(columns-list (mapcar #'read-from-string
(cl-ppcre:split ";" header)))
(number-of-columns (length columns-list))
(column-names (make-array number-of-columns
:initial-contents columns-list))
(rownames (make-array 1 :adjustable t :fill-pointer 0))
(values (make-array 1 :adjustable t :fill-pointer 0)))
(set-syntax-from-char #\; #\ )
(loop
:for reader = (read stream nil stream)
:until (eq reader stream)
:do (progn (vector-push-extend reader row-names)
(loop
:for count :from 2 :upto number-of-columns
:do (vector-push-extend (read stream nil)
values)))
:finally (return (values row-names
column-names
values)))))
Note: I wouldn't use set-syntax-from-char in real code, I am using it just for the sake of this example.
I suspect that the I/O is the slowest part here. You can probably get faster I/O if you use READ-SEQUENCE rather than calling READ-LINE repeatedly. So your code might look something like this:
(with-open-file (s "my-file.csv")
(let* ((len (file-length s))
(data (make-array len)))
(read-sequence data s)
data))
Then split data by newlines and add your logic.
Whether that helps or not, it'd helpful for you to profile your code, e.g. with :sb-sprof, to see where most of the time is being spent.
To read csv files, I find very useful and fast the cl-csv package (https://github.com/AccelerationNet/cl-csv). For instance, to solve your problem, the following code could be used:
(let ((data (cl-csv:read-csv #P"my-file.csv" :separator #\;)))
(values (apply #'vector (first data))
(apply #'vector (rest (mapcar #'first data)))
(apply #'vector
(mapcar #'read-from-string (loop :for row :in (rest data)
:append (rest row))))))
cl-csv:read-csv returns a list contaning, for each row, a list of strings that are the contents of the cells.
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
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))