Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fetch: Support object literal serialization (as JSON) #213

Open
friday opened this issue Nov 20, 2017 · 9 comments
Open

Fetch: Support object literal serialization (as JSON) #213

friday opened this issue Nov 20, 2017 · 9 comments

Comments

@friday
Copy link
Contributor

friday commented Nov 20, 2017

Since #128 was fixed, the built-in object serialization was removed in favor of either diy-serialization or hooks.

I'm proposing to reintroduce object serialization, but as JSON, not query strings, because of the many issues and implementation details to settle with query strings.

Problems with query strings

  • Converting nested data to to query strings is somewhat complex, likely adding at least ~20-30 lines of code.
  • Some form of recursion is needed to optimally "walk" over the object. Tail call optimisation is currently only implemented in Chrome (I think?) so this could cause stack overflows on huge nested objects.
  • The only supported data type is string, so it's not possible on the receiving end to distinguish between "true" vs true or 5 vs "5" or ["a"] vs {0:'a'}.
  • It's not a standardized format. From the jQuery docs: "Because there is no universally agreed-upon specification for param strings, it is not possible to encode complex data structures using this method in a manner that works ideally across all languages supporting such input. Use JSON format as an alternative for encoding complex data instead."
  • Because it's not a universally agreed-upon spec, many serializers have optional arguments for small details such as how to encode arrays, or encoding spaces as + or %20. Besides making the serializer more complex and harder to both maintain and use, this also means fetch would have to support these arguments as well, or only support the defaults. I think this partly defeats the purpose of supporting object serialization in fetch.

JSON (application/json) doesn't have these problems

  • No custom method needed. JSON.stringify() is supported since IE8.
  • No stack overflow risk
  • No ambiguity or string coercion.
  • No difference between server implementations or extra arguments needed.
  • Very few implementation details to settle, making it very easy to implement.

On the negative side, it could be confusing if strings are treated as query strings (current behavior) but object literals are serialized as application/json (this proposal). Depending on your server environment it could also take some extra effort to support.

@friday
Copy link
Contributor Author

friday commented Nov 20, 2017

I'm already wrapping Bliss.fetch, so I'm not in a hurry to get this added. I mainly wanted to share my discoveries (I tried to solve this using query strings at first).

Converting ex: {foo: {bar: 1, x: {y: 'z'}} to foo[bar=1]&foo[x][y]=z requires recursion. In the simplest form, something like this:

// Note: This is simplified/dumb version of https://github.com/friday/query-string-encode

function queryStringEncode(input) {
    // Array of path/value tuples
    var flattened = [];

    (function flatten(input, path) {
        // Add path and value to flat array
        if ([Boolean, Number, String].indexOf(input.constructor) !== -1) {
            var serializedPath = path.map(function (key, index) {
                return index ? "[" + key + "]" : key;
            }).join("");
            flattened.push([serializedPath, input]);
        }

        // Iterate over next level of array/object
        else if ([Array, Object].indexOf(input.constructor) !== -1) {
            for (var key in input) {
                flatten(input[key], path.concat([key]));
            }
        }
    })(input, []);

    // Convert array to query string
    return flattened.map(function (pair) {
        return pair.map(encodeURIComponent).join("=");
    }).join("&");
}

@LeaVerou
Copy link
Owner

The thing is, sometimes we want to send the actual object, e.g. uploading File objects.
So we need to be careful where we apply this stringification.

@friday
Copy link
Contributor Author

friday commented Nov 20, 2017

Yes. Only object literals or objects created with new Object() should be supported imo. This can be checked using the constructor (and probably many other ways)

({}).constructor === Object // true
new Blob().constructor === Object // false
new FormData.constructor === Object // false

@LeaVerou
Copy link
Owner

Use the Bliss $.type() function. Your check would fail if the object comes from another window/frame.

@friday
Copy link
Contributor Author

friday commented Nov 20, 2017

Oh yeah, I recall something about this. Seems this applies to my suggestion as well :(

@LeaVerou
Copy link
Owner

Seems this applies to my suggestion as well :(

What do you mean?

@friday
Copy link
Contributor Author

friday commented Nov 20, 2017

Oh, I read that too quickly. Nevermind the "as well" part of the comment.

@joyously
Copy link

joyously commented Nov 7, 2020

Is the fetch function still needed? Is it stated somewhere what range of browser versions this is now intended for?

@friday
Copy link
Contributor Author

friday commented Nov 8, 2020

Perhaps not @joyously. However Bliss's Fetch implementation is not a polyfill. It's different from the Fetch standard in small ways that means people who use it can't just drop-in replace the native browser Fetch or a polyfill.

The browser support for Fetch is very good today. When the Fetch API standard was released and got initial browser support you couldn't get any upload/download progress or abort the request. AbortController and ReadableStream were added later to solve that. They're also well supported today, but this adds to more differences in the Fetch API implementation and Bliss's Fetch.

However you're probably right that this issue may not be important today anymore. I'll leave that decision to Lea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants