How to send url encoded arrays and hash tables?
Closed this issue · 6 comments
Just a question: what's the way to send url encoded lists/arrays and hash tables?
For example an array or list:
answer[0].name = 2
answer[1].name = 3
answer[2].name = 5
answer[3].name = 4
answer[4].name = 10
And a hash table
dict.name = "john"
dict.age = "19"
For some reason it's only working as alists for me, and when I tried embedding them it broke?
I basically just need the common case of sending a list of hashtables. I tried turning the hash tables to alists before sending them, but for some reason the fact that it was a list of them made the encoding not work.
Is this not part of the functionality? Or am I missing something?
ok, so quri doesn't support this.
See here: https://github.com/fukamachi/quri/blob/master/src/encode.lisp#L91
I think this is quite a common use case, see for example https://docs.stripe.com/api/checkout/sessions/create?lang=curl
Where the use case is sending a value which is a list of dictionaries.
Would you accept a PR?
I just thought for a second to check how libraries out there cope with this and found this: https://github.com/boogsbunny/stripe/blob/master/src/query.lisp
They are basically building most of the functionality that I would add here.
Is this a convention of how to encode and that's why quri doesn't deal with it? Is it for efficiency? Or maybe it's just that nobody did it? I would be happy to submit a PR
Can you provide examples of what is lacking and the API you're proposing?
url-encode-params
seems general enough to accommodate for all use cases. Why should quri specialize on the cdr of each pair of params-alist
?
This is a sample query which is difficult to do with quri:
curl https://someurl.com/api/something \
-d "line_items[0][price]"=24 \
-d "line_items[0][quantity]"=2 \
-d mode=payment
For some data as follows (represented here with json for ease of understanding since there's no standard hash table representation in CL)
{
line_items: [
{
price: "12",
quantity: 2,
},
{
price: "1",
quantity: 10,
},
{
price: "24",
quantity: 4,
},
{
price: "39",
quantity: 1,
},
];
}
To pass this to quri, even if we have an association list
'(("line_items" .
((("price" . "12") ("quantity" . 2))
(("price" . "1") ("quantity" . 10))
(("price" . "24") ("quantity" . 4))
(("price" . "39") ("quantity" . 1)))))
Does not work.
quri does not deal with converting from lists to a notation bracketed with indices, and it does not deal with embedded data at all.
Currently the valid association list can only be done by previously "flattening" the given data structure with the appropriate labels corresponding to the data.
This would need to be cleaned, but this is my working code that is working. It's scratch code, I haven't fully cleaned it up, but once I do I can add it as a PR or you can use this if you want as a starting point:
(defgeneric encode-key-value (key value))
(defmethod encode-key-value ((key string) (value string))
(cons key value))
(defmethod encode-key-value (key (value (eql nil)))
(cons (format nil "~A" key) "false"))
(defmethod encode-key-value (key (value (eql t)))
(cons (format nil "~A" key) "true"))
(defmethod encode-key-value (key (value string))
(cons (format nil "~A" key) value))
(defmethod encode-key-value (key value)
(cons (format nil "~A" key) (format nil "~A" value)))
(defmethod encode-key-value (key (value list))
(let ((result '()))
(if (assoc-utils:alistp value)
(loop for (alist-key . alist-val) in value
for encoded = (encode-key-value (format nil "~A[~A]" key alist-key)
alist-val)
do (if (consp (cdr encoded))
(setf result (append result encoded))
(push encoded result)))
(loop for element in value
for i from 0
for encoded = (encode-key-value (format nil "~A[~A]" key i) element)
do (if (consp (cdr encoded))
(setf result (append result encoded))
(push encoded result))))
result))
(defmethod encode-key-value (key (value standard-object))
(encode-key-value key (util-clos:object-to-hash-table value)))
(defmethod encode-key-value (key (value hash-table))
(loop for ht-key being each hash-key of value
using (hash-value ht-value)
collect (encode-key-value (format nil "~A[~A]" key ht-key) ht-value)))
(defgeneric encode-form-content (content))
(defmethod encode-form-content ((content hash-table))
(let ((result '()))
(loop for key being each hash-key of content
using (hash-value value)
for encoded = (encode-key-value key value)
do (if (consp (cdr encoded))
(setf result (append result encoded))
(push encoded result)))
result))
(defmethod encode-form-content ((content list))
(unless (assoc-utils:alistp content)
(error "Provided data ~A is not an association list AKA alist." content))
(let ((result '()))
(loop for (key . value) in content
for encoded = (encode-key-value key value)
do (if (consp (cdr encoded))
(setf result (append result encoded))
(push encoded result)))
result))
(encode-form-content
(serapeum:dict "somedataneeded" "someothervalue"
"data1" "somevalue"
"line_items" (list (serapeum:dict "price" "12"
"quantity" 1)
(serapeum:dict "price" "24"
"quantity" 1))))
(encode-form-content
(list (cons "somedataneeded" "someothervalue")
(cons "data1" "somevalue")
(cons "line_items" (list (serapeum:dict "price" "12"
"quantity" 1)
(serapeum:dict "price" "24"
"quantity" 1)))))
And here are the results
CORE> (encode-form-content
(serapeum:dict "somedata" "somevalue"
"some_other_data" "some_other_value"
"line_items" (list (serapeum:dict "price" "12"
"quantity" 1)
(serapeum:dict "price" "24"
"quantity" 1))))
(("some_other_data" . "some_other_value") ("somedata" . "somevalue")
("line_items[0][price]" . "12")
("line_items[0][quantity]" . "1")
("line_items[1][price]" . "24")
("line_items[1][quantity]" . "1"))
CORE> (encode-form-content
(list (cons "somedata" "somevalue")
(cons "some_other_data" "some_other_value")
(cons "line_items" (list (serapeum:dict "price" "12"
"quantity" 1)
(serapeum:dict "price" "24"
"quantity" 1)))))
(("some_other_data" . "some_other_value") ("somedata" . "somevalue")
("line_items[0][price]" . "12")
("line_items[0][quantity]" . "1")
("line_items[1][price]" . "24")
("line_items[1][quantity]" . "1"))
By the way, I know quri is concerned with performance and the above code may be optimized. I'm not well versed in CL code optimization.
@daninus14 I think this is outside the scope of quri, which doesn't concern POST requests.
ok thanks, I guess the right place is dexador