Search code examples
javascriptjsonhtmx

How to send object data in hx-post - JSON.stringify is surrounding arrays with quotes


I'm trying to send data via REST API from a client to a server, the client is just an HTML page with JavaScript and HTMX in it, the server is Spring Boot. The endpoint accepts POST requests with an object:

public record FileMetadataDTO(Integer availabilityTime, FileDTO[] files) {}

public record FileDTO(String name, String extension, Integer sizeMB) {}

On the client side I have a button that sends the request to the server:

    <button id="upload-button"
                    hx-post="http://localhost:8080/api/v1/upload/metadata"
                    hx-headers='{"Content-Type": "application/json"}'
                    hx-ext="json-enc"
                    hx-vals='js:{"availabilityTime": 1, "files": getFilesDTO()}'
                    style="display: none;">Upload</button>
            <label for="upload-button" style="border: 1px solid #ccc; padding: 6px 12px; cursor: pointer;">Upload files</label>

The getFilesDTO() function returns a stringified array:

function getFilesDTO() {
            let filesDTO = [];
            for (let i = 0; i < tuckedFiles.length; i++) {
                filesDTO.push({
                    name: tuckedFiles[i].name,
                    extension: tuckedFiles[i].name.split('.').pop(),
                    sizeMB: tuckedFiles[i].size / 1024 / 1024
                });
            }
            return JSON.stringify(filesDTO);
        }

But JSON.stringify surrounds the array with quotes and the server cannot deserialize it:

{"availabilityTime":"1","files":"[{\"name\":\"a.txt\",\"extension\":\"txt\",\"sizeMB\":0.00022220611572265625},{\"name\":\"another_file.txt\",\"extension\":\"txt\",\"sizeMB\":0.000003814697265625}]"}

A valid payload would be:

{"availabilityTime":"1","files":[{"name":"a.txt","extension":"txt","sizeMB":0.00022220611572265625},{"name":"another_file.txt","extension":"txt","sizeMB":0.000003814697265625}]}

But I can't make JSON.stringify do it like that no matter what. I tried it in the console of Chrome and Edge to see if my code is at fault, but JSON.stringify([]) in the console of both browsers returns '[]'.

How can I fix this? I've been on multiple websites and can't find a solution.


Solution

  • It may be that you have not imported the json-enc extension.

    But also whatever is returned from getFilesDTO() is what is added to the payload, so don't return a string if you don't want a string!

    If I replicate the code, adding an explicit import (after the htmx import), it works ok.

    express server

    app.get('/', (req, res) => {
      res.send(`
        <!DOCTYPE html>
        <html>
          <head>
            ...
            <script src="https://unpkg.com/htmx.org"></script>
            <script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>
    

    getFilesDTO()

        <script>
          // dummy files for testing 
          const files = [
            {name: "abc.js", size: 100 * 1024 * 1024},
            {name: "def.js", size: 200 * 1024 * 1024}
          ]
          function getFilesDTO() {
            return files.map((file) => {
              return {
                name: file.name,
                extension: file.name.split('.').pop(),
                sizeMB: file.size / 1024 / 1024
              }
            })
          }
        </script>
    

    In the console network tab, it shows a non-quoted files array

    enter image description here


    Alternatively you can <script> in the json-enc code (it's quite small) and use that to debug

        <script>
          htmx.defineExtension('json-enc', {
            onEvent: function (name, evt) {
              if (name === "htmx:configRequest") {
                evt.detail.headers['Content-Type'] = "application/json";
              }
            },
            
            encodeParameters : function(xhr, parameters, elt) {
              console.log('parameters', parameters)
              xhr.overrideMimeType('text/json');
              return (JSON.stringify(parameters));
            }
           });
        </script>