This specification defines a new form encoding algorithm that enables the transmission of
form data as JSON. Instead of capturing form data as essentially an array of key-value pairs
which is the bread and butter of existing form encodings, it relies on a simple
name
attribute syntax that makes it possible to capture rich data structures
as JSON directly.
This specification is an extension specification to HTML.
JSON is commonly used as an exchange format between Web client and backend services. Enabling HTML forms to submit JSON directly simplifies implementation as it enables backend services to operate by accepting a single input format that is what's more able to encode richer structure than other form encodings (where structure has traditional had to be emulated).
User agents that implement this specification will transmit JSON data from their forms
whenever the form's enctype
attribute is set to application/json
.
During the transition period, user agents that do not support this encoding will fall back
to using application/x-www-form-urlencoded
. This can be detected on the server
side, and the conversion algorithm described in this specification can be used to convert
such data to JSON.
The path format used in input name
s is straightforward. To begin with, when
no structuring information is present, the information will simply be captured as keys in
a JSON object:
<form enctype='application/json'> <input name='name' value='Bender'> <select name='hind'> <option selected>Bitable</option> <option>Kickable</option> </select> <input type='checkbox' name='shiny' checked> </form> // produces { "name": "Bender" , "hind": "Bitable" , "shiny": true }
If a path is repeated, its value is captured as an array:
<form enctype='application/json'> <input type='number' name='bottle-on-wall' value='1'> <input type='number' name='bottle-on-wall' value='2'> <input type='number' name='bottle-on-wall' value='3'> </form> // produces { "bottle-on-wall": [1, 2, 3] }
Deeper structures can be produced using sub-keys in the path, using either string keys for objects or integer keys for arrays:
<form enctype='application/json'> <input name='pet[species]' value='Dahut'> <input name='pet[name]' value='Hypatia'> <input name='kids[1]' value='Thelma'> <input name='kids[0]' value='Ashley'> </form> // produces { "pet": { "species": "Dahut" , "name": "Hypatia" } , "kids": ["Ashley", "Thelma"] }
As you can see above, the keys for array values can be in any order. If the array is somehow
sparse, then null
values are inserted:
<form enctype='application/json'> <input name='hearbeat[0]' value='thunk'> <input name='hearbeat[2]' value='thunk'> </form> // produces { "hearbeat": ["thunk", null, "thunk"] }
Paths can cause structures to nest to arbitrary depths:
<form enctype='application/json'> <input name='pet[0][species]' value='Dahut'> <input name='pet[0][name]' value='Hypatia'> <input name='pet[1][species]' value='Felis Stultus'> <input name='pet[1][name]' value='Billie'> </form> // produces { "pet": [ { "species": "Dahut" , "name": "Hypatia" } , { "species": "Felis Stultus" , "name": "Billie" } ] }
Really, any depth you might need.
<form enctype='application/json'> <input name='wow[such][deep][3][much][power][!]' value='Amaze'> </form> // produces { "wow": { "such": { "deep": [ null , null , null , { "much": { "power": { "!": "Amaze" } } } ] } } }
The algorithm does not lose data in that every piece of information ends up being submitted. But given the path syntax, it is possible to introduce clashes such that one may attempt to set an object, an array, and a scalar value on the same key.
As seen in a previous example, trying to set multiple scalars on the same key will convert the value into an array. Trying to set a scalar value at a path that also contains an object will cause the scalar to be set on that object with the empty string key. Trying to set an array value at a path that also contains an object will cause the non-null values of that array to be set on the object using their array indices as keys. This is exemplified below:
<form enctype='application/json'> <input name='mix' value='scalar'> <input name='mix[0]' value='array 1'> <input name='mix[2]' value='array 2'> <input name='mix[key]' value='key key'> <input name='mix[car]' value='car key'> </form> // produces { "mix": { "": "scalar" , "0": "array 1" , "2": "array 2" , "key": "key key" , "car": "car key" } }
This may seem somewhat convoluted but it should be considered as a resilience mechanism meant to ensure that data is not lost rather than the normal usage of the JSON encoding.
As we have seen above, multiple values with the same key are upgraded to an array, and it is also possible to directly use array offsets. However there are cases in which when generating a form from existing data, one may not know if there will be one or more instances of a given key (so that without using indices one will get back at times a scalar, at times an array) and it can be slightly cumbersome to properly generate array indices (especially if the field may be modified on the client side, which would mean maintaining array indices properly there). In order to indicate that a given path must contain an array irrespective of the number of its items, and without resorting to indices, one may use the append notation (only as the final step in a path):
<form enctype='application/json'> <input name='highlander[]' value='one'> </form> // produces { "highlander": ["one"] }
The JSON encoding also supports file uploads. The values of files are themselves structured
as objects and contain a type
field indicating the MIME type, a
name
field containing the file name, and a body
field with the
file's content as base64.
<form enctype='application/json'> <input type='file' name='file' multiple> </form> // assuming the user has selected two text files, produces: { "file": [ { "type": "text/plain", "name": "dahut.txt", "body": "REFBQUFBQUFIVVVVVVVVVVVVVCEhIQo=" }, { "type": "text/plain", "name": "litany.txt", "body": "SSBtdXN0IG5vdCBmZWFyLlxuRmVhciBpcyB0aGUgbWluZC1raWxsZXIuCg==" } ] }
Still in the spirit of not losing information, whenever a path makes use of an invalid syntax, it is simply used whole as if it were just a key with no structure:
<form enctype='application/json'> <input name='error[good]' value='BOOM!'> <input name='error[bad' value='BOOM BOOM!'> </form> // Produces: { "error": { "good": "BOOM!" } , "error[bad": "BOOM BOOM!" }
The following terms are defined in the HTML specification. [[!html51]]
accept-charset
attribute.
The following terms are defined in ECMAScript. [[!ECMA-262]]
JSONObject
and
JSONArray
.
application/json
encoding algorithm
For the purposes of the algorithms below, an Object
corresponds to
the in-memory representation for a JSONObject
and an
Array
corresponds to the in-memory representation for a
JSONArray
.
The following algorithm encodes form data as application/json
. It operates
on the form data set obtained from constructing the form data set.
Object
.file
, set the is file flag.
The algorithm above deliberately ignores any charset information (e.g. from
accept-charset
) and always encodes the resulting JSON as UTF-8. This is
an intentionally sane behaviour.
The steps to parse a JSON encoding path are as follows:
The steps to set a JSON encoding value are as follows:
Object
have its "name" property set to the file's name, its
"type" property set to the file's type, and its "body" property set to the Base64 encoding
of the file's body. [[!RFC2045]]
undefined
, run the following subsubsteps:
Array
containing
entry value as its only member.
Array
, then get the
context's property named by the step's key and push
entry value onto it.
Object
and the is
file flag is not set, then run the steps to set a JSON encoding value
with context set to the current value; a step with its type set to
"object", its key set to the empty string, and its last flag set; current value set to
the current value's property named by the empty string; the entry
value; and the is file flag. Return the result.
Array
containing current value and
entry value, in this order.
undefined
, run the following subsubsteps:
Object
, then return
the value of the context's property named by the step's key.
Array
, then rub the
following subsubsteps:
Object
.undefined
then set a
property of object named i to item.
Object
with a property named by
the empty string set to current value.
Given that there exist deployed services using JSON and ambient authentication, and given
that form requests are not protected by the same-origin policy by default, if this
encoding were left wide open then a number of attacks would become possible. Because of
this, when using the application/json
form encoding the same-origin policy is
enforced.
When the
form
submission algorithm is invoked in order to
Submit
as entity with enctype set to application/json
and entity
body set to the result of applying the application/json
encoding
algorithm, causing the browsing context to
navigate,
the user agent MUST invoke the
fetch
algorithm with the force same-origin flag.
Thanks to Philippe Le Hégaret for serving as a sounding board for the first version of the encoding algorithm.