htmx
htmx copied to clipboard
hx-vals with nested array
HTMX version: 2.0.4
Encountered an issue when attempting to send a list of objects with hx-vals
<button hx-post="/query" hx-vals='{"list": [{"key": "value"}]}'>test</button>
This results in request with this formdata:
list=%22%5Bobject%20Object%5D%22
Unescaped: list="[object Object]"
Non-objects in the list work as I would expect.
Somewhat surprisingly, nesting another object makes it work as I wanted:
<button hx-post="/query" hx-vals='{"parent": {"list": [{"key": "value"}]}}'>test</button>
parent=%7B%22list%22%3A%5B%7B%22key%22%3A%22value%22%7D%5D%7D
Unescaped: parent={"list":[{"key":"value"}]}
Is this a bug?
htmx uses formData objects to store and parse the form data used for parameters to match the native browser way of processing form parameters. Form data is a way of storing key value string pairs only so any values you supply will be cast to string internally. you can't really send json style data via forms in browsers normally just strings that you need to format like json. It will internally perform toString() function calls but as you have found arrays of objects default to return [object Object] in javascript. however when the form data object value is a {} object with nested data this happens to act more like normal JSON.stringify().
Really the intention of hx-vals is to set an object with only a list of top level field names set to strings so you would do something like:
hx-vals='{"list": "[{"key": "value"}]"}'
where you wrap your value as a string. But this quickly falls apart because of all the escaping required to allow so many levels of nested " marks!!!
you could use the eval version of hx-vals maybe like:
hx-vals='js:{"list": JSON.stringify([{"key": "value"}])}'
Because forms are just text fields it is best to try and match this where possible and avoid JSON but there are some htmx json extensions you can try and work around some of these limitations here https://htmx.org/extensions/
I understand that form data is not really supposed to be used this way and I have several workarounds to the underlying problem I'm designing for - most of which are worse than embedding json in form data. While workarounds are helpful, it is not what I'm after in this issue.
however when the form data object value is a {} object with nested data this happens to act more like normal JSON.stringify().
The json stringification is not accidental, as your comment seems to imply: https://github.com/bigskysoftware/htmx/commit/cd6d2b78e2deb19391ecedd38128aca7477ed8b9 JSON.stringify() was explicitly introduced to deal with nested objects. https://github.com/bigskysoftware/htmx/issues/772
What this comes down to is that the hx-vals docs state that it takes a JSON value, but this doesn't actually work for arbitrary JSON leading to this surprising behavior in the developer experience.
Therefore I argue that either a) The docs should be updated to reflect the behavior or b) The library should always call JSON.stringify() on hx-vals values - since a JSON value should always be valid JSON, and this is the proper way to serialize
or possibly c) It was a mistake to say that hx-vals accepts JSON, and instead both docs and implementation should be updated to take a key-value list instead.