├── .gitignore ├── README.md ├── css ├── OpenSans-CondLight.eot ├── OpenSans-CondLight.ttf ├── OpenSans-CondLight.woff ├── formic.css └── OpenSans-CondLight.svg ├── NOTES.txt ├── LICENSE ├── index.html ├── json ├── index.html └── json.js ├── cursed ├── cursed.js └── index.html ├── formic.js └── specs └── json ├── index.html └── FPWD-20140522.html /.gitignore: -------------------------------------------------------------------------------- 1 | existing 2 | scratch/ 3 | IDEAS.txt 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | formic 2 | ====== 3 | 4 | Playing with forms 5 | -------------------------------------------------------------------------------- /css/OpenSans-CondLight.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darobin/formic/HEAD/css/OpenSans-CondLight.eot -------------------------------------------------------------------------------- /css/OpenSans-CondLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darobin/formic/HEAD/css/OpenSans-CondLight.ttf -------------------------------------------------------------------------------- /css/OpenSans-CondLight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darobin/formic/HEAD/css/OpenSans-CondLight.woff -------------------------------------------------------------------------------- /NOTES.txt: -------------------------------------------------------------------------------- 1 | 2 | STUFF: 3 | - enctype=application/json 4 | - use structured field names as commonly employed in PHP 5 | - foo -> { foo: "val" } 6 | - foo, foo (multiple fields with that name, select, etc.) -> { foo: ["val", "val"] } 7 | - foo[1] -> { foo: [null, "val"] } 8 | - foo[bar] -> { foo: { bar: "val" }} 9 | - foo[bar][0] -> { foo: { bar: ["val"] }} 10 | - steps are created along the way 11 | - this can create conflicts 12 | - 13 | 14 | -> { foo: { "": "x", bar: "y" }} 15 | - 16 | 17 | -> { foo: { 0: "x", bar: "y" }} 18 | - 19 | 20 | -> { foo: { "": "x", 0: "y" }} 21 | - if error in the syntax, just use the string as is 22 | 23 | - Improved FormData? 24 | -
25 | - repeats and templates 26 | - matching common PHP semantics 27 | - DnD 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Robin Berjon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | formic 6 | 7 | 8 | 9 |
10 |

formic

11 |
12 |
13 |

14 | This is the repository I use to carry out toy hacking with forms and editing HTML 15 | technologies in order to figure out ideas as to what can be done. 16 |

17 |

18 | Current experiments include: 19 |

20 | 23 |

24 | Specifications to go with: 25 |

26 | 29 |

30 | Feedback is welcome on anything you might find here. Don't use anything you find in 31 | production unless you really know what you're doing. 32 |

33 |

34 | Past experiments: 35 |

36 | 39 |
40 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /css/formic.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face { 3 | font-family: "Open Sans"; 4 | src: url("OpenSans-CondLight.eot"); 5 | src: url("OpenSans-CondLight.eot?#iefix") format("embedded-opentype"), 6 | url("OpenSans-CondLight.woff") format("woff"), 7 | url("OpenSans-CondLight.ttf") format("truetype"), 8 | url("OpenSans-CondLight.svg#802dcb78fec1f63545a1c51edf3ba5e0") format("svg"); 9 | font-style: normal; 10 | font-weight: 200; 11 | } 12 | 13 | html, body { 14 | margin: 0; 15 | padding: 0; 16 | font-family: "Open Sans", sans-serif; 17 | color: #003; 18 | min-height: 100%; 19 | } 20 | 21 | header { 22 | background: rgb(43, 182, 247); 23 | color: #fff; 24 | } 25 | 26 | h1, h2, h3 { 27 | font-weight: normal; 28 | } 29 | 30 | h1 { 31 | font-size: 6em; 32 | margin: 0; 33 | } 34 | 35 | h2 { 36 | font-size: 2.5em; 37 | margin-top: 0; 38 | } 39 | 40 | nav, section, header, footer { 41 | max-width: 600px; 42 | padding: 1em; 43 | margin: auto; 44 | } 45 | 46 | section { 47 | line-height: 160%; 48 | } 49 | 50 | a { 51 | color: rgb(43, 182, 247); 52 | } 53 | 54 | footer { 55 | background: rgb(43, 182, 247); 56 | color: #fff; 57 | padding-bottom: 1em; 58 | } 59 | 60 | footer > * { 61 | margin: auto; 62 | } 63 | 64 | footer h3 { 65 | margin-top: 0; 66 | margin-bottom: -0.6em; 67 | padding: 0; 68 | } 69 | 70 | ul { 71 | list-style-type: circle; 72 | } 73 | -------------------------------------------------------------------------------- /json/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | formic — json 6 | 7 | 8 | 9 |
10 |

json

11 |
12 |
13 |

14 | Return home. 15 |

16 |

17 | This is an experiment to see if it's possible (and useful) to support 18 | enctype="application/json" on forms. 19 |

20 |
21 |
22 |

Example

23 | 24 | 25 | 66 | 67 | 68 |

69 |     
70 | 74 | 75 | 76 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /cursed/cursed.js: -------------------------------------------------------------------------------- 1 | /*global CustomEvent*/ 2 | 3 | // IMPROVEMENT 4 | // forget about boundary detection for now, only do: 5 | // - when there is a selection change, dispatch an event with the old and new. If it is cancelled, 6 | // set selection to the old 7 | // - when typing happens, dispatch an event. If it is not cancelled, let it happen, otherwise do. 8 | // - note that typing and all need to know what they apply to, notably deletion or insertion 9 | // - maybe just use an "editable" attribute for this 10 | // - demo usage with an MVC approach, including some basic editing commands, intercepting the 11 | // Enter key 12 | 13 | // XXX 14 | // fire an event indicating direction 15 | 16 | // BUG: this implementation does not work in WebKit or Blink, IE not tested yet 17 | // BUG: when the cursor is at the start of a line, its top position is reported as being 18 | // on the previous line. This is clearly a bug in Gecko. It causes the problem that 19 | // going up detects hitting the top boundary too early; going down fails to detect it 20 | // the first time 21 | // maybe I can get better results for the SOL bug by measuring the top on another 22 | // range that is made to contain the letter *after* the offset? 23 | (function () { 24 | var top = NaN 25 | , savedRange 26 | , wasCollapsed 27 | ; 28 | function details (ev) { 29 | var kc = ev.keyCode 30 | , ret = { 31 | sel: window.getSelection() 32 | , range: window.getSelection().getRangeAt(0) 33 | , up: kc === 38 34 | , down: kc === 40 35 | , left: kc === 37 36 | , right: kc === 39 37 | } 38 | ; 39 | ret.arrow = ret.up || ret.down || ret.left || ret.right; 40 | ret.upDown = ret.up || ret.down; 41 | return ret; 42 | } 43 | function rangeDown (ev) { 44 | var d = details(ev); 45 | if (!d.upDown) return; 46 | if (ev.shiftKey) return; 47 | var rect = d.range.getBoundingClientRect(); 48 | top = rect.top; 49 | wasCollapsed = d.range.collapsed; 50 | d.sel.modify("move", d.up ? "backward" : "forward", "line"); 51 | savedRange = d.range.cloneRange(); 52 | } 53 | function rangePress (ev) { 54 | var d = details(ev); 55 | if (d.upDown && !ev.shiftKey) { 56 | var rect = d.range.getBoundingClientRect() 57 | , atBoundary = ((top - rect.top) === 0) 58 | , collapseChanged = !wasCollapsed && d.range.collapsed 59 | ; 60 | if (atBoundary && !collapseChanged) { 61 | // console.log("BOUNDARY:" + (d.up ? "top" : "bottom")); 62 | ev.currentTarget.dispatchEvent(new CustomEvent("cursed-boundary", { detail: { side: (d.up ? "top" : "bottom") }})); 63 | d.sel.removeAllRanges(); 64 | d.sel.addRange(savedRange); 65 | } 66 | ev.preventDefault(); 67 | } 68 | if (!d.arrow) ev.preventDefault(); 69 | } 70 | 71 | 72 | window.curse = function (el) { 73 | el.contentEditable = true; 74 | el.addEventListener("keydown", rangeDown, false); 75 | el.addEventListener("keypress", rangePress, false); 76 | }; 77 | window.bless = function (el) { 78 | el.contentEditable = false; 79 | el.removeEventListener("keydown", rangeDown, false); 80 | el.removeEventListener("keypress", rangePress, false); 81 | }; 82 | window.curseAll = function (doc) { 83 | if (!doc) doc = document; 84 | Array.prototype.forEach.call(doc.querySelectorAll("[cursed]"), window.curse); 85 | }; 86 | }()); 87 | -------------------------------------------------------------------------------- /cursed/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | formic — cursed 6 | 7 | 8 | 9 |
10 |

cursed

11 |
12 |
13 |

14 | Return home. 15 |

16 |

17 | One option that could help fix contentEditable, suggested by our friends at 18 | substance, would be to simply expose a primitive way of 19 | making an element accept focus and a cursor, but have this cursor do nothing at all apart 20 | from being movable. None of the DOM manipulations possible with contentEditable 21 | such as inserting characters, deleting, running commands is possible. Those are entirely 22 | the responsibility of code written to handle them. 23 |

24 |

25 | While this may seem like a step back, it can actually provide a far more reliable entry 26 | point with which to write editors. The less the browser does, the fewer bugs there will 27 | be. It also enables a simple MVC approach whereby script intercepts events that intend to 28 | manipulate the content, use those to modify a data model that is not the underlying 29 | DOM, and that data model change then causes a rendering change. It may seem indirect but is 30 | actually a much saner way of handling editing. 31 |

32 |

33 | This behaviour is enabled by setting a cursed attribute on a given element 34 | (while I await inspiration as to a better name). 35 |

36 |

37 | In addition to simple, selecting but not editing behaviour, whenever the user tries to move 38 | the caret across the top or bottom boundary of a given element an event is triggered 39 | (currently called cursed-boundary) with ev.detail.side set to 40 | top or bottom. The point of this event is to enable the use case 41 | in which individual elements are made cursed but in between each of those is an item that 42 | must not be editable (possibly some affordance), thereby making it less convenient to set 43 | their common parent as cursed. The boundary event can be caught so as to move the focus 44 | between editable siblings. 45 |

46 |

47 | The current behaviour of selections and ranges in browsers is so much all over the place 48 | (and in many case so useless) that a proper polyfill for this is probably impossible. 49 |

50 |

51 | WARNING: This code works in Gecko, it is known 52 | NOT to work in Blink and WebKit. IE has not been 53 | tested yet. 54 |

55 |

56 | Gecko also has an appalling selection bug that causes it to report the position of the 57 | cursor at the end of the previous line when it is in fact at the start of the current. This 58 | causes wrong behaviours: when arrowing up or down it can detect boundary too early, 59 | paralysing the cursor. There is no code workaround that I knew to devise. Arrow right to get 60 | unstuck. 61 |

62 |
63 |
64 |

Example

65 |

66 | All of the paragraphs in this section have been cursed. A red border gets added when the 67 | boundary event is triggered. 68 |

69 |

70 | The preceding sections have demonstrated that information can be measured on different 71 | scales: in hits, in bits, in dollars. The next question: is there some property that should 72 | be common to all the information measures? 73 |

74 |

75 | To begin with, we imagine two newspapers, one in English and the other in Chinese, both 76 | giving the same account of an event. Then, objectively speaking, both newspapers contain the 77 | same amount of information about the event. Still, an Englishman may be of the opinion that 78 | only the English paper contains information, because he does not understand Chinese. If we 79 | allow such subjective judgements, anybody may have any opinion on the information content in 80 | a message, and no rules apply. But, what if we want to measure the information content of 81 | the message itself, disconnected from the shortcomings of a receiver? 82 |

83 |

84 | We have already asked "Information about what?". Now we ask "Information to whom?". The true 85 | information content of a message can be imagined as the information received by somebody 86 | with a complete understanding, who is aptly called the ideal receiver in semantic 87 | information theory. 88 |

89 |
90 | 94 | 95 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /json/json.js: -------------------------------------------------------------------------------- 1 | /*global formic*/ 2 | 3 | (function () { 4 | function isArray (obj) { 5 | return Object.prototype.toString.call(obj) === "[object Array]"; 6 | } 7 | function isObject (obj) { 8 | if (isArray(obj)) return false; 9 | if (typeof obj !== "object") return false; 10 | if (obj.constructor && !obj.constructor.prototype.hasOwnProperty("isPrototypeOf")) return false; 11 | return true; 12 | } 13 | function parseSteps (name) { 14 | var steps = [] 15 | , orig = name // keep in case parsing fails 16 | , ok = false 17 | ; 18 | name = name.replace(/^([^\[]+)/, function (m, p1) { 19 | steps.push({ type: "object", key: p1 }); 20 | ok = true; 21 | return ""; 22 | }); 23 | if (!ok) return [{ type: "object", key: orig, last: true }]; 24 | if (!name.length) { 25 | steps[0].last = true; 26 | return steps; 27 | } 28 | 29 | while (name.length) { 30 | var ok = false; 31 | name = name.replace(/^\[\]/, function () { 32 | steps[steps.length - 1].append = true; 33 | ok = true; 34 | return ""; 35 | }); 36 | // we had a match, but appends can only occur at the end 37 | if (ok) { 38 | if (name.length) return [{ type: "object", key: orig, last: true }]; 39 | break; 40 | } 41 | name = name.replace(/^\[(\d+)\]/, function (m, p1) { 42 | steps.push({ type: "array", key: (1 * p1) }); 43 | ok = true; 44 | return ""; 45 | }); 46 | if (ok) continue; 47 | name = name.replace(/^\[([^\]]+)\]/, function (m, p1) { 48 | steps.push({ type: "object", key: p1 }); 49 | ok = true; 50 | return ""; 51 | }); 52 | if (ok) continue; 53 | return [{ type: "object", key: orig, last: true }]; 54 | } 55 | 56 | for (var i = 0, n = steps.length; i < n; i++) { 57 | var step = steps[i]; 58 | if (i + 1 < n) step.next = steps[i + 1].type; 59 | else step.last = true; 60 | } 61 | return steps; 62 | } 63 | function setValue (context, step, current, value, isFile) { 64 | if (step.last) { 65 | // there is no key, just set it 66 | if (current === undefined) context[step.key] = step.append ? [value] : value; 67 | // there are already multiple keys, push it 68 | else if (isArray(current)) context[step.key].push(value); 69 | // we're trying to set a scalar on an object 70 | else if (isObject(current) && !isFile) { 71 | return setValue(current, { type: "object", key: "", last: true }, current[""], value, isFile); 72 | } 73 | // there's already a scalar, pimp to array 74 | else context[step.key] = [current, value]; 75 | return context; 76 | } 77 | else { 78 | // there is no key, just define a new object 79 | if (current === undefined) return context[step.key] = (step.next === "array" ? [] : {}); 80 | // it's already an object 81 | else if (isObject(current)) return context[step.key]; 82 | // there is an array, we convert its defined items to an object 83 | else if (isArray(current)) { 84 | if (step.next === "array") return current; 85 | else { 86 | var obj = {}; 87 | for (var i = 0, n = current.length; i < n; i++) { 88 | var item = current[i]; 89 | if (item !== undefined) obj[i] = item; 90 | } 91 | return context[step.key] = obj; 92 | } 93 | } 94 | // there is a scalar 95 | else { 96 | return context[step.key] = { "": current }; 97 | } 98 | } 99 | } 100 | window.applicationJSON = function (form, cb) { 101 | var data = formic.formDataSet(form, { booleanChecked: true }) 102 | , ret = {} 103 | , files = [] 104 | , read = 0 105 | ; 106 | for (var i = 0, n = data.length; i < n; i++) { 107 | var item = data[i]; 108 | if (item.value && item.value.body) files.push(item.value); 109 | } 110 | function done () { 111 | for (var i = 0, n = data.length; i < n; i++) { 112 | var item = data[i] 113 | , isFile = item.value && item.value.body !== undefined 114 | , steps = parseSteps(item.name) 115 | , cur = ret 116 | ; 117 | for (var j = 0, m = steps.length; j < m; j++) { 118 | var step = steps[j]; 119 | cur = setValue(cur, step, cur[step.key], item.value, isFile); 120 | } 121 | } 122 | cb(ret); 123 | } 124 | function readNextFile (f) { 125 | var fr = new FileReader(); 126 | fr.onloadend = function () { 127 | if (fr.error) throw fr.error; 128 | f.body = fr.result.replace(/^.*?,/, ""); 129 | read++; 130 | if (read === files.length) done(); 131 | }; 132 | fr.readAsDataURL(f.body); 133 | } 134 | for (var i = 0, n = files.length; i < n; i++) readNextFile(files[i]); 135 | if (!files.length) done(); 136 | }; 137 | }()); 138 | 139 | // XXX 140 | // - use structured field names as commonly employed in PHP 141 | // - foo -> { foo: "val" } 142 | // - foo, foo (multiple fields with that name, select, etc.) -> { foo: ["val", "val"] } 143 | // - foo[1] -> { foo: [null, "val"] } 144 | // - foo[bar] -> { foo: { bar: "val" }} 145 | // - foo[bar][0] -> { foo: { bar: ["val"] }} 146 | // - steps are created along the way 147 | // - this can create conflicts 148 | // - 149 | // 150 | // -> { foo: { "": "x", bar: "y" }} 151 | // - 152 | // 153 | // -> { foo: { 0: "x", bar: "y" }} 154 | // - 155 | // 156 | // -> { foo: { "": "x", 0: "y" }} 157 | // - if error in the syntax, just use the string as is 158 | -------------------------------------------------------------------------------- /formic.js: -------------------------------------------------------------------------------- 1 | 2 | // this is largely a set of facilities to help with the manipulating of forms 3 | // ideally, some of this would be made part of the platform 4 | 5 | (function () { 6 | var filter = Array.prototype.filter 7 | , forEach = Array.prototype.forEach 8 | ; 9 | var formic = { 10 | matches: function (el, sels, refs) { 11 | return ( 12 | el.matches || 13 | el.mozMatchesSelector || 14 | el.webkitMatchesSelector || 15 | el.msMatchesSelector 16 | ).call(el, sels, refs); 17 | } 18 | , ancestors: function (el) { 19 | var ret = []; 20 | while (el.parentNode.nodeType === Node.ELEMENT_NODE) { 21 | ret.push(el.parentNode); 22 | el = el.parentNode; 23 | } 24 | return ret; 25 | } 26 | , submittableElements: function (form) { 27 | return filter.call( document.querySelectorAll("button, input, keygen, object, select, textarea") 28 | , function (el) { 29 | return form === el.form; 30 | } 31 | ); 32 | } 33 | , disabled: function (el) { 34 | return formic.matches(el, ":disabled"); 35 | } 36 | , formDataSet: function (form, options) { 37 | options = options || {}; 38 | var controls = formic.submittableElements(form) 39 | , dataSet = [] 40 | ; 41 | for (var i = 0, n = controls.length; i < n; i++) { 42 | var field = controls[i]; 43 | // Fields To Skip 44 | // We don't check that objects must have loaded a plugin. Bite me. 45 | // Since this is batched, we also don't know the submitter and therefore ignore them all 46 | if (formic.ancestors(field).filter(function (el) { formic.matches(el, "datalist"); }).length) continue; 47 | if (formic.disabled(field)) continue; 48 | if (formic.matches(field, "button")) continue; 49 | if (field.type === "submit" || field.type === "image" || field.type === "button") continue; 50 | if (field.type === "checkbox" && !field.checked) continue; 51 | if (field.type === "radio" && !field.checked) continue; 52 | if (!field.name) continue; 53 | var type = field.type, name = field.name; 54 | 55 | if (formic.matches(field, "select")) { 56 | // XXX this should be "> option, > optgroup > option" but it can't be 57 | forEach.call(field.querySelectorAll("option") 58 | , function (el) { 59 | if (!el.disabled && el.selected) 60 | dataSet.push({ 61 | name: name 62 | , type: type 63 | , value: el.value 64 | , el: el 65 | }); 66 | } 67 | ); 68 | } 69 | 70 | else if (type === "checkbox" || type === "radio") { 71 | var value; 72 | if (field.hasAttribute("value")) value = field.getAttribute("value"); 73 | else if (options.booleanChecked) value = true; 74 | else value = "on"; 75 | dataSet.push({ 76 | name: name 77 | , type: type 78 | , value: value 79 | , el: field 80 | }); 81 | } 82 | 83 | else if (type === "file") { 84 | if (field.files.length === 0) { 85 | dataSet.push({ name: name, type: "application/octet-stream", value: "", el: field }); 86 | } 87 | else { 88 | forEach.call(field.files 89 | , function (f) { 90 | dataSet.push({ 91 | name: name 92 | , type: type 93 | , value: { 94 | type: f.type 95 | , name: f.name 96 | , body: f 97 | } 98 | , el: field 99 | }); 100 | } 101 | ); 102 | } 103 | } 104 | // here we could process . but we won't 105 | 106 | else { 107 | dataSet.push({ 108 | name: name 109 | , type: type 110 | , value: field.value 111 | , el: field 112 | }); 113 | } 114 | 115 | if (type === "textarea" || type === "text" || type === "search") { 116 | if (field.getAttribute("dirname")) { 117 | // here we should fully determine the directionality, but we cheat 118 | dataSet.push({ 119 | name: field.getAttribute("dirname") 120 | , type: "direction" 121 | , value: field.dir || "auto" 122 | , el: field 123 | }); 124 | } 125 | } 126 | } 127 | 128 | // clean up the CRLF 129 | for (var i = 0, n = dataSet.length; i < n; i++) { 130 | var d = dataSet[i]; 131 | if (d.type === "textarea" || d.type === "file") continue; 132 | if (typeof d.value !== "string") continue; 133 | d.value = d.value 134 | .replace(/\r(?!\n)/g, "\r\n") 135 | .split("").reverse().join("") // where the fuck are my lookbehinds? 136 | .replace(/\n(?!\r)/, "\n\r") 137 | .split("").reverse().join("") 138 | ; 139 | } 140 | 141 | return dataSet; 142 | } 143 | }; 144 | 145 | window.formic = formic; 146 | }()); 147 | -------------------------------------------------------------------------------- /specs/json/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | W3C HTML JSON form submission 6 | 7 | 26 | 36 | 37 | 38 |
39 |

40 | This specification defines a new form encoding algorithm that enables the transmission of 41 | form data as JSON. Instead of capturing form data as essentially an array of key-value pairs 42 | which is the bread and butter of existing form encodings, it relies on a simple 43 | name attribute syntax that makes it possible to capture rich data structures 44 | as JSON directly. 45 |

46 |
47 |
48 |

49 | This specification is an 50 | extension 51 | specification to HTML. 52 |

53 |
54 |
55 |

Introduction

56 |

57 | JSON is commonly used as an exchange format between Web client and backend services. 58 | Enabling HTML forms to submit JSON directly simplifies implementation as it enables backend 59 | services to operate by accepting a single input format that is what's more able to encode 60 | richer structure than other form encodings (where structure has traditional had to be 61 | emulated). 62 |

63 |

64 | User agents that implement this specification will transmit JSON data from their forms 65 | whenever the form's enctype attribute is set to application/json. 66 | During the transition period, user agents that do not support this encoding will fall back 67 | to using application/x-www-form-urlencoded. This can be detected on the server 68 | side, and the conversion algorithm described in this specification can be used to convert 69 | such data to JSON. 70 |

71 |

72 | The path format used in input names is straightforward. To begin with, when 73 | no structuring information is present, the information will simply be captured as keys in 74 | a JSON object: 75 |

76 |
 77 |         <form enctype='application/json'>
 78 |           <input name='name' value='Bender'>
 79 |           <select name='hind'>
 80 |             <option selected>Bitable</option>
 81 |             <option>Kickable</option>
 82 |           </select>
 83 |           <input type='checkbox' name='shiny' checked>
 84 |         </form>
 85 | 
 86 |         // produces
 87 |         {
 88 |           "name":   "Bender"
 89 |         , "hind":   "Bitable"
 90 |         , "shiny":  true
 91 |         }
 92 |       
93 |

94 | If a path is repeated, its value is captured as an array: 95 |

96 |
 97 |         <form enctype='application/json'>
 98 |           <input type='number' name='bottle-on-wall' value='1'>
 99 |           <input type='number' name='bottle-on-wall' value='2'>
100 |           <input type='number' name='bottle-on-wall' value='3'>
101 |         </form>
102 | 
103 |         // produces
104 |         {
105 |           "bottle-on-wall":   [1, 2, 3]
106 |         }
107 |       
108 |

109 | Deeper structures can be produced using sub-keys in the path, using either string keys for 110 | objects or integer keys for arrays: 111 |

112 |
113 |         <form enctype='application/json'>
114 |           <input name='pet[species]' value='Dahut'>
115 |           <input name='pet[name]' value='Hypatia'>
116 |           <input name='kids[1]' value='Thelma'>
117 |           <input name='kids[0]' value='Ashley'>
118 |         </form>
119 | 
120 |         // produces
121 |         {
122 |             "pet":  {
123 |                 "species":  "Dahut"
124 |             ,   "name":     "Hypatia"
125 |             }
126 |         ,   "kids":   ["Ashley", "Thelma"]
127 |         }
128 |       
129 |

130 | As you can see above, the keys for array values can be in any order. If the array is somehow 131 | sparse, then null values are inserted: 132 |

133 |
134 |         <form enctype='application/json'>
135 |           <input name='hearbeat[0]' value='thunk'>
136 |           <input name='hearbeat[2]' value='thunk'>
137 |         </form>
138 | 
139 |         // produces
140 |         {
141 |             "hearbeat":   ["thunk", null, "thunk"]
142 |         }
143 |       
144 |

145 | Paths can cause structures to nest to arbitrary depths: 146 |

147 |
148 |         <form enctype='application/json'>
149 |           <input name='pet[0][species]' value='Dahut'>
150 |           <input name='pet[0][name]' value='Hypatia'>
151 |           <input name='pet[1][species]' value='Felis Stultus'>
152 |           <input name='pet[1][name]' value='Billie'>
153 |         </form>
154 | 
155 |         // produces
156 |         {
157 |             "pet":  [
158 |                 {
159 |                     "species":  "Dahut"
160 |                 ,   "name":     "Hypatia"
161 |                 }
162 |             ,   {
163 |                     "species":  "Felis Stultus"
164 |                 ,   "name":     "Billie"
165 |                 }
166 |             ]
167 |         }
168 |       
169 |

170 | Really, any depth you might need. 171 |

172 |
173 |         <form enctype='application/json'>
174 |           <input name='wow[such][deep][3][much][power][!]' value='Amaze'>
175 |         </form>
176 | 
177 |         // produces
178 |         {
179 |             "wow":  {
180 |                 "such": {
181 |                     "deep": [
182 |                         null
183 |                     ,   null
184 |                     ,   null
185 |                     ,   {
186 |                             "much": {
187 |                                 "power": {
188 |                                     "!":  "Amaze"
189 |                                 }
190 |                             }
191 |                         }
192 |                     ]
193 |                 }
194 |             }
195 |         }
196 |       
197 |

198 | The algorithm does not lose data in that every piece of information ends up being submitted. 199 | But given the path syntax, it is possible to introduce clashes such that one may attempt 200 | to set an object, an array, and a scalar value on the same key. 201 |

202 |

203 | As seen in a previous example, trying to set multiple scalars 204 | on the same key will convert the value into an array. Trying to set a scalar value at a path 205 | that also contains an object will cause the scalar to be set on that object with the 206 | empty string key. Trying to set an array value at a path that also contains an object will 207 | cause the non-null values of that array to be set on the object using their array indices 208 | as keys. This is exemplified below: 209 |

210 |
211 |         <form enctype='application/json'>
212 |           <input name='mix' value='scalar'>
213 |           <input name='mix[0]' value='array 1'>
214 |           <input name='mix[2]' value='array 2'>
215 |           <input name='mix[key]' value='key key'>
216 |           <input name='mix[car]' value='car key'>
217 |         </form>
218 | 
219 |         // produces
220 |         {
221 |             "mix":  {
222 |                 "":     "scalar"
223 |             ,   "0":    "array 1"
224 |             ,   "2":    "array 2"
225 |             ,   "key":  "key key"
226 |             ,   "car":  "car key"
227 |             }
228 |         }
229 |       
230 |

231 | This may seem somewhat convoluted but it should be considered as a resilience mechanism 232 | meant to ensure that data is not lost rather than the normal usage of the JSON encoding. 233 |

234 |

235 | As we have seen above, multiple values with the same key 236 | are upgraded to an array, and it is also possible to directly 237 | use array offsets. However there are cases in which when generating a form from existing 238 | data, one may not know if there will be one or more instances of a given key (so that 239 | without using indices one will get back at times a scalar, at times an array) and it can be 240 | slightly cumbersome to properly generate array indices (especially if the field may be 241 | modified on the client side, which would mean maintaining array indices properly there). In 242 | order to indicate that a given path must contain an array irrespective of the number of its 243 | items, and without resorting to indices, one may use the append notation (only as the final 244 | step in a path): 245 |

246 |
247 |         <form enctype='application/json'>
248 |           <input name='highlander[]' value='one'>
249 |         </form>
250 | 
251 |         // produces
252 |         {
253 |             "highlander":  ["one"]
254 |         }
255 |       
256 |

257 | The JSON encoding also supports file uploads. The values of files are themselves structured 258 | as objects and contain a type field indicating the MIME type, a 259 | name field containing the file name, and a body field with the 260 | file's content as base64. 261 |

262 |
263 |         <form enctype='application/json'>
264 |           <input type='file' name='file' multiple>
265 |         </form>
266 | 
267 |         // assuming the user has selected two text files, produces:
268 |         {
269 |             "file": [
270 |                 {
271 |                     "type": "text/plain",
272 |                     "name": "dahut.txt",
273 |                     "body": "REFBQUFBQUFIVVVVVVVVVVVVVCEhIQo="
274 |                 },
275 |                 {
276 |                     "type": "text/plain",
277 |                     "name": "litany.txt",
278 |                     "body": "SSBtdXN0IG5vdCBmZWFyLlxuRmVhciBpcyB0aGUgbWluZC1raWxsZXIuCg=="
279 |                 }
280 |             ]
281 |         }
282 |       
283 |

284 | Still in the spirit of not losing information, whenever a path makes use of an invalid 285 | syntax, it is simply used whole as if it were just a key with no structure: 286 |

287 |
288 |         <form enctype='application/json'>
289 |           <input name='error[good]' value='BOOM!'>
290 |           <input name='error[bad' value='BOOM BOOM!'>
291 |         </form>
292 | 
293 |         // Produces:
294 |         {
295 |             "error": {
296 |                 "good":   "BOOM!"
297 |             }
298 |         ,   "error[bad":  "BOOM BOOM!"
299 |         }
300 |       
301 |
302 | 303 |
304 | 305 |
306 |

Terminology

307 |

308 | The following terms are defined in the HTML specification. [[!html51]] 309 |

310 | 324 |

325 | The following terms are defined in ECMAScript. [[!ECMA-262]] 326 |

327 | 339 |
340 | 341 |
342 |

The application/json encoding algorithm

343 |

344 | For the purposes of the algorithms below, an Object corresponds to 345 | the in-memory representation for a JSONObject and an 346 | Array corresponds to the in-memory representation for a 347 | JSONArray. 348 |

349 |

350 | The following algorithm encodes form data as application/json. It operates 351 | on the form data set obtained from constructing the form data set. 352 |

353 |
    354 |
  1. Let resulting object be a new Object.
  2. 355 |
  3. 356 | For each entry in the form data set, perform these substeps: 357 |
      358 |
    1. If the entry's type is file, set the is file flag.
    2. 359 |
    3. 360 | Let steps be the result of running the steps to parse a JSON encoding 361 | path on the entry's name. 362 |
    4. 363 |
    5. Let context be set to the value of resulting object.
    6. 364 |
    7. 365 | For each step in the list of steps, run the following subsubsteps: 366 |
        367 |
      1. 368 | Let the current value be the value obtained by getting the step's key 369 | from the current context. 370 |
      2. 371 |
      3. 372 | Run the steps to set a JSON encoding value with the current 373 | context, the step, the current value, the entry's value, and 374 | the is file flag. 375 |
      4. 376 |
      5. 377 | Update context to be the value returned by the steps to set a JSON 378 | encoding value ran above. 379 |
      6. 380 |
      381 |
    8. 382 |
    383 |
  4. 384 |
  5. 385 | Let result be the value returned from calling the stringify operation 386 | with resulting object as its first parameter and the two remaining parameters 387 | left undefined. 388 |
  6. 389 |
  7. 390 | Encode result as UTF-8 and return the resulting byte stream. 391 |
  8. 392 |
393 |

394 | The algorithm above deliberately ignores any charset information (e.g. from 395 | accept-charset) and always encodes the resulting JSON as UTF-8. This is 396 | an intentionally sane behaviour. 397 |

398 | 399 |

400 | The steps to parse a JSON encoding path are as follows: 401 |

402 |
    403 |
  1. Let path be the path we are to parse.
  2. 404 |
  3. Let original be a copy of path.
  4. 405 |
  5. Let steps be an empty list of steps.
  6. 406 |
  7. 407 | Let first key be the result of 408 | collecting a sequence of characters that 409 | are not U+005B LEFT SQUARE BRACKET ("[") from the path. 410 |
  8. 411 |
  9. 412 | If first key is empty, jump to the step labelled failure below. 413 |
  10. 414 |
  11. 415 | Otherwise remove the collected characters from path and push a step onto 416 | steps with its type set to "object", its key set to the collected characters, 417 | and its last flag unset. 418 |
  12. 419 |
  13. 420 | If the path is empty, set the last flag on the last step in steps 421 | and return steps. 422 |
  14. 423 |
  15. 424 | Loop: 425 | While path is not an empty string, run these substeps: 426 |
      427 |
    1. 428 | If the first two characters in path are U+005B LEFT SQUARE BRACKET ("[") 429 | followed by U+005D RIGHT SQUARE BRACKET ("]"), run these subsubsteps: 430 |
        431 |
      1. Set the append flag on the last step in steps.
      2. 432 |
      3. Remove those two characters from path.
      4. 433 |
      5. 434 | If there are characters left in path, jump to the step labelled 435 | failure below. 436 |
      6. 437 |
      7. Otherwise jump to the step labelled loop above.
      8. 438 |
      439 |
    2. 440 |
    3. 441 | If the first character in path is U+005B LEFT SQUARE BRACKET ("["), 442 | followed by one or more ASCII digits, followed by U+005D RIGHT SQUARE BRACKET 443 | ("]"), run these subsubsteps: 444 |
        445 |
      1. Remove the first character from path.
      2. 446 |
      3. 447 | Collect a sequence of characters being ASCII digits, remove them 448 | from path, and let numeric key be the result of interpreting 449 | them as a base-ten integer. 450 |
      4. 451 |
      5. Remove the following character from path.
      6. 452 |
      7. 453 | Push a step onto steps with its type set to "array", its key set to the 454 | numeric key, and its last flag unset. 455 |
      8. 456 |
      9. Jump to the step labelled loop above.
      10. 457 |
      458 |
    4. 459 |
    5. 460 | If the first character in path is U+005B LEFT SQUARE BRACKET ("["), 461 | followed by one or more characters that are not U+005D RIGHT SQUARE BRACKET, 462 | followed by U+005D RIGHT SQUARE BRACKET ("]"), run these subsubsteps: 463 |
        464 |
      1. Remove the first character from path.
      2. 465 |
      3. 466 | Collect a sequence of characters that are not U+005D RIGHT SQUARE 467 | BRACKET, remove them from path, and let object key be the 468 | result. 469 |
      4. 470 |
      5. Remove the following character from path.
      6. 471 |
      7. 472 | Push a step onto steps with its type set to "object", its key set to 473 | the object key, and its last flag unset. 474 |
      8. 475 |
      9. Jump to the step labelled loop above.
      10. 476 |
      477 |
    6. 478 |
    7. 479 | If this point in the loop is reached, jump to the step labelled failure 480 | below. 481 |
    8. 482 |
    483 |
  16. 484 |
  17. 485 | For each step in steps, run the following substeps: 486 |
      487 |
    1. If the step is the last step, set its last flag.
    2. 488 |
    3. Otherwise, set its next type to the type of the next step in steps.
    4. 489 |
    490 |
  18. 491 |
  19. Return steps.
  20. 492 |
  21. 493 | Failure: return a list of steps containing a single step with its type 494 | set to "object", its key set to original, and its last flag set. 495 |
  22. 496 |
497 | 498 |

499 | The steps to set a JSON encoding value are as follows: 500 |

501 |
    502 |
  1. Let context be the context this algorithm is called with.
  2. 503 |
  3. Let step be the step of the path this algorithm is called with.
  4. 504 |
  5. Let current value be the current value this algorithm is called with.
  6. 505 |
  7. Let entry value be the entry value this algorithm is called with.
  8. 506 |
  9. Let is file be the is file flag this algorithm is called with.
  10. 507 |
  11. 508 | If is file is set then replace entry value with an 509 | Object have its "name" property set to the file's name, its 510 | "type" property set to the file's type, and its "body" property set to the Base64 encoding 511 | of the file's body. [[!RFC2045]] 512 |
  12. 513 |
  13. 514 | If step has its last flag set, run the following substeps: 515 |
      516 |
    1. 517 | If current value is undefined, run the following subsubsteps: 518 |
        519 |
      1. 520 | If step's append flag is set, set the context's property 521 | named by the step's key to a new Array containing 522 | entry value as its only member. 523 |
      2. 524 |
      3. 525 | Otherwise, set the context's property named by the step's 526 | key to entry value. 527 |
      4. 528 |
      529 |
    2. 530 |
    3. 531 | Else if current value is an Array, then get the 532 | context's property named by the step's key and push 533 | entry value onto it. 534 |
    4. 535 |
    5. 536 | Else if current value is an Object and the is 537 | file flag is not set, then run the steps to set a JSON encoding value 538 | with context set to the current value; a step with its type set to 539 | "object", its key set to the empty string, and its last flag set; current value set to 540 | the current value's property named by the empty string; the entry 541 | value; and the is file flag. Return the result. 542 |
    6. 543 |
    7. 544 | Otherwise, set the context's property named by the step's 545 | key to an Array containing current value and 546 | entry value, in this order. 547 |
    8. 548 |
    9. 549 | Return context. 550 |
    10. 551 |
    552 |
  14. 553 |
  15. 554 | Otherwise, run the following substeps: 555 |
      556 |
    1. 557 | If current value is undefined, run the following subsubsteps: 558 |
        559 |
      1. 560 | If step's next type is "array", set the context's property 561 | named by the step's key to a new empty Array and 562 | return it. 563 |
      2. 564 |
      3. 565 | Otherwise,set the context's property named by the step's key 566 | to a new empty Object and return it. 567 |
      4. 568 |
      569 |
    2. 570 |
    3. 571 | Else if current value is an Object, then return 572 | the value of the context's property named by the step's key. 573 |
    4. 574 |
    5. 575 | Else if current value is an Array, then rub the 576 | following subsubsteps: 577 |
        578 |
      1. If step's next type is "array", return current value.
      2. 579 |
      3. 580 | Otherwise, run the following subsubsubsteps: 581 |
          582 |
        1. Let object be a new empty Object.
        2. 583 |
        3. 584 | For each item and zero-based index i in current 585 | value, if item is not undefined then set a 586 | property of object named i to item. 587 |
        4. 588 |
        5. 589 | Otherwise, set the context's property named by the 590 | step's key to object. 591 |
        6. 592 |
        7. Return object.
        8. 593 |
        594 |
      4. 595 |
      596 |
    6. 597 |
    7. 598 | Otherwise, run the following subsubsteps: 599 |
        600 |
      1. 601 | Let object be a new Object with a property named by 602 | the empty string set to current value. 603 |
      2. 604 |
      3. 605 | Set the context's property named by the step's 606 | key to object. 607 |
      4. 608 |
      5. Return object.
      6. 609 |
      610 |
    8. 611 |
    612 |
  16. 613 |
614 |
615 |
616 |

Form Submission

617 |

618 | Given that there exist deployed services using JSON and ambient authentication, and given 619 | that form requests are not protected by the same-origin policy by default, if this 620 | encoding were left wide open then a number of attacks would become possible. Because of 621 | this, when using the application/json form encoding the same-origin policy is 622 | enforced. 623 |

624 |

625 | When the 626 | form 627 | submission algorithm is invoked in order to 628 | Submit 629 | as entity with enctype set to application/json and entity 630 | body set to the result of applying the application/json encoding 631 | algorithm, causing the browsing context to 632 | navigate, 634 | the user agent MUST invoke the 635 | fetch 636 | algorithm with the force same-origin flag. 637 |

638 |
639 |
640 |

Acknowledgements

641 |

642 | Thanks to Philippe Le Hégaret for serving as a sounding board for the first version of the 643 | encoding algorithm. 644 |

645 | 646 |
647 | 648 | 649 | -------------------------------------------------------------------------------- /css/OpenSans-CondLight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Created by FontPrep 20130207 at Fri Jan 31 13:15:04 2014 6 | By Robin Berjon 7 | Digitized data copyright (c) 2011, Google Corporation. 8 | 9 | 10 | 11 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 45 | 48 | 51 | 53 | 55 | 57 | 59 | 61 | 63 | 65 | 67 | 69 | 71 | 73 | 75 | 78 | 80 | 82 | 85 | 87 | 90 | 93 | 95 | 97 | 99 | 101 | 103 | 106 | 109 | 111 | 113 | 115 | 117 | 119 | 121 | 123 | 125 | 127 | 129 | 131 | 133 | 135 | 137 | 139 | 141 | 143 | 145 | 148 | 150 | 152 | 154 | 156 | 158 | 160 | 162 | 164 | 166 | 168 | 170 | 172 | 174 | 177 | 179 | 181 | 183 | 185 | 187 | 191 | 193 | 195 | 197 | 199 | 201 | 203 | 205 | 207 | 209 | 211 | 213 | 216 | 218 | 220 | 222 | 224 | 226 | 228 | 230 | 232 | 234 | 236 | 238 | 240 | 242 | 244 | 246 | 249 | 251 | 253 | 256 | 258 | 261 | 264 | 266 | 268 | 270 | 273 | 275 | 277 | 279 | 281 | 283 | 285 | 287 | 289 | 291 | 293 | 295 | 297 | 299 | 301 | 304 | 307 | 310 | 312 | 314 | 316 | 319 | 322 | 325 | 327 | 330 | 332 | 334 | 336 | 338 | 340 | 342 | 344 | 346 | 348 | 351 | 354 | 357 | 360 | 363 | 366 | 368 | 371 | 373 | 375 | 377 | 380 | 382 | 384 | 387 | 390 | 393 | 396 | 399 | 402 | 405 | 408 | 411 | 414 | 417 | 420 | 423 | 425 | 427 | 429 | 431 | 434 | 437 | 440 | 443 | 446 | 449 | 452 | 454 | 457 | 459 | 461 | 463 | 466 | 468 | 471 | 474 | 475 | 476 | -------------------------------------------------------------------------------- /specs/json/FPWD-20140522.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | W3C HTML JSON form submission 6 | 7 | 8 | 18 | 212 | 277 |

Abstract

278 |

279 | This specification defines a new form encoding algorithm that enables the transmission of 280 | form data as JSON. Instead of capturing form data as essentially an array of key-value pairs 281 | which is the bread and butter of existing form encodings, it relies on a simple 282 | name attribute syntax that makes it possible to capture rich data structures 283 | as JSON directly. 284 |

285 |

Status of This Document

286 | 287 | 288 | 289 |

290 | This section describes the status of this document at the time of its publication. 291 | Other documents may supersede this document. A list of current W3C publications and the 292 | latest revision of this technical report can be found in the W3C technical reports index at 293 | http://www.w3.org/TR/. 294 |

295 | 296 |

297 | This specification is an 298 | extension 299 | specification to HTML. 300 |

301 | 302 |

303 | This document was published by the HTML Working Group as a First Public Working Draft. 304 | 305 | This document is intended to become a W3C Recommendation. 306 | 307 | 308 | If you wish to make comments regarding this document, please send them to 309 | public-html@w3.org 310 | (subscribe, 311 | archives). 312 | 313 | 314 | 315 | 316 | All comments are welcome. 317 | 318 |

319 | 320 | 321 |

322 | Publication as a First Public Working Draft does not imply endorsement by the W3C 323 | Membership. This is a draft document and may be updated, replaced or obsoleted by other 324 | documents at any time. It is inappropriate to cite this document as other than work in 325 | progress. 326 |

327 | 328 | 329 | 330 |

331 | 332 | This document was produced by a group operating under the 333 | 5 February 2004 W3C Patent 334 | Policy. 335 | 336 | 337 | 338 | 339 | W3C maintains a public list of any patent 340 | disclosures 341 | 342 | made in connection with the deliverables of the group; that page also includes 343 | instructions for disclosing a patent. An individual who has actual knowledge of a patent 344 | which the individual believes contains 345 | Essential 346 | Claim(s) must disclose the information in accordance with 347 | section 348 | 6 of the W3C Patent Policy. 349 | 350 | 351 |

352 | 353 | 354 | 355 | 356 |

Table of Contents

357 | 358 |
359 |

1. Introduction

This section is non-normative.

360 |

361 | JSON is commonly used as an exchange format between Web client and backend services. 362 | Enabling HTML forms to submit JSON directly simplifies implementation as it enables backend 363 | services to operate by accepting a single input format that is what's more able to encode 364 | richer structure than other form encodings (where structure has traditional had to be 365 | emulated). 366 |

367 |

368 | User agents that implement this specification will transmit JSON data from their forms 369 | whenever the form's enctype attribute is set to application/json. 370 | During the transition period, user agents that do not support this encoding will fall back 371 | to using application/x-www-form-urlencoded. This can be detected on the server 372 | side, and the conversion algorithm described in this specification can be used to convert 373 | such data to JSON. 374 |

375 |

376 | The path format used in input names is straightforward. To begin with, when 377 | no structuring information is present, the information will simply be captured as keys in 378 | a JSON object: 379 |

380 |
Example 1: Basic Keys
<form enctype='application/json'>
381 |   <input name='name' value='Bender'>
382 |   <select name='hind'>
383 |     <option selected>Bitable</option>
384 |     <option>Kickable</option>
385 |   </select>
386 |   <input type='checkbox' name='shiny' checked>
387 | </form>
388 | 
389 | // produces
390 | {
391 |   "name":   "Bender"
392 | , "hind":   "Bitable"
393 | , "shiny":  true
394 | }
395 |

396 | If a path is repeated, its value is captured as an array: 397 |

398 |
Example 2: Multiple Values
<form enctype='application/json'>
399 |   <input type='number' name='bottle-on-wall' value='1'>
400 |   <input type='number' name='bottle-on-wall' value='2'>
401 |   <input type='number' name='bottle-on-wall' value='3'>
402 | </form>
403 | 
404 | // produces
405 | {
406 |   "bottle-on-wall":   [1, 2, 3]
407 | }
408 |

409 | Deeper structures can be produced using sub-keys in the path, using either string keys for 410 | objects or integer keys for arrays: 411 |

412 |
Example 3: Deeper Structure
<form enctype='application/json'>
413 |   <input name='pet[species]' value='Dahut'>
414 |   <input name='pet[name]' value='Hypatia'>
415 |   <input name='kids[1]' value='Thelma'>
416 |   <input name='kids[0]' value='Ashley'>
417 | </form>
418 | 
419 | // produces
420 | {
421 |     "pet":  {
422 |         "species":  "Dahut"
423 |     ,   "name":     "Hypatia"
424 |     }
425 | ,   "kids":   ["Ashley", "Thelma"]
426 | }
427 |

428 | As you can see above, the keys for array values can be in any order. If the array is somehow 429 | sparse, then null values are inserted: 430 |

431 |
Example 4: Sparse Arrays
<form enctype='application/json'>
432 |   <input name='hearbeat[0]' value='thunk'>
433 |   <input name='hearbeat[2]' value='thunk'>
434 | </form>
435 | 
436 | // produces
437 | {
438 |     "hearbeat":   ["thunk", null, "thunk"]
439 | }
440 |

441 | Paths can cause structures to nest to arbitrary depths: 442 |

443 |
Example 5: Even Deeper
<form enctype='application/json'>
444 |   <input name='pet[0][species]' value='Dahut'>
445 |   <input name='pet[0][name]' value='Hypatia'>
446 |   <input name='pet[1][species]' value='Felis Stultus'>
447 |   <input name='pet[1][name]' value='Billie'>
448 | </form>
449 | 
450 | // produces
451 | {
452 |     "pet":  [
453 |         {
454 |             "species":  "Dahut"
455 |         ,   "name":     "Hypatia"
456 |         }
457 |     ,   {
458 |             "species":  "Felis Stultus"
459 |         ,   "name":     "Billie"
460 |         }
461 |     ]
462 | }
463 |

464 | Really, any depth you might need. 465 |

466 |
Example 6: Such Deep
<form enctype='application/json'>
467 |   <input name='wow[such][deep][3][much][power][!]' value='Amaze'>
468 | </form>
469 | 
470 | // produces
471 | {
472 |     "wow":  {
473 |         "such": {
474 |             "deep": [
475 |                 null
476 |             ,   null
477 |             ,   null
478 |             ,   {
479 |                     "much": {
480 |                         "power": {
481 |                             "!":  "Amaze"
482 |                         }
483 |                     }
484 |                 }
485 |             ]
486 |         }
487 |     }
488 | }
489 |

490 | The algorithm does not lose data in that every piece of information ends up being submitted. 491 | But given the path syntax, it is possible to introduce clashes such that one may attempt 492 | to set an object, an array, and a scalar value on the same key. 493 |

494 |

495 | As seen in a previous example, trying to set multiple scalars 496 | on the same key will convert the value into an array. Trying to set a scalar value at a path 497 | that also contains an object will cause the scalar to be set on that object with the 498 | empty string key. Trying to set an array value at a path that also contains an object will 499 | cause the non-null values of that array to be set on the object using their array indices 500 | as keys. This is exemplified below: 501 |

502 |
Example 7: Merge Behaviour
<form enctype='application/json'>
503 |   <input name='mix' value='scalar'>
504 |   <input name='mix[0]' value='array 1'>
505 |   <input name='mix[2]' value='array 2'>
506 |   <input name='mix[key]' value='key key'>
507 |   <input name='mix[car]' value='car key'>
508 | </form>
509 | 
510 | // produces
511 | {
512 |     "mix":  {
513 |         "":     "scalar"
514 |     ,   "0":    "array 1"
515 |     ,   "2":    "array 2"
516 |     ,   "key":  "key key"
517 |     ,   "car":  "car key"
518 |     }
519 | }
520 |

521 | This may seem somewhat convoluted but it should be considered as a resilience mechanism 522 | meant to ensure that data is not lost rather than the normal usage of the JSON encoding. 523 |

524 |

525 | As we have seen above, multiple values with the same key 526 | are upgraded to an array, and it is also possible to directly 527 | use array offsets. However there are cases in which when generating a form from existing 528 | data, one may not know if there will be one or more instances of a given key (so that 529 | without using indices one will get back at times a scalar, at times an array) and it can be 530 | slightly cumbersome to properly generate array indices (especially if the field may be 531 | modified on the client side, which would mean maintaining array indices properly there). In 532 | order to indicate that a given path must contain an array irrespective of the number of its 533 | items, and without resorting to indices, one may use the append notation (only as the final 534 | step in a path): 535 |

536 |
Example 8: Append
<form enctype='application/json'>
537 |   <input name='highlander[]' value='one'>
538 | </form>
539 | 
540 | // produces
541 | {
542 |     "highlander":  ["one"]
543 | }
544 |

545 | The JSON encoding also supports file uploads. The values of files are themselves structured 546 | as objects and contain a type field indicating the MIME type, a 547 | name field containing the file name, and a body field with the 548 | file's content as base64. 549 |

550 |
Example 9: Files
<form enctype='application/json'>
551 |   <input type='file' name='file' multiple>
552 | </form>
553 | 
554 | // assuming the user has selected two text files, produces:
555 | {
556 |     "file": [
557 |         {
558 |             "type": "text/plain",
559 |             "name": "dahut.txt",
560 |             "body": "REFBQUFBQUFIVVVVVVVVVVVVVCEhIQo="
561 |         },
562 |         {
563 |             "type": "text/plain",
564 |             "name": "litany.txt",
565 |             "body": "SSBtdXN0IG5vdCBmZWFyLlxuRmVhciBpcyB0aGUgbWluZC1raWxsZXIuCg=="
566 |         }
567 |     ]
568 | }
569 |

570 | Still in the spirit of not losing information, whenever a path makes use of an invalid 571 | syntax, it is simply used whole as if it were just a key with no structure: 572 |

573 |
Example 10: Files
<form enctype='application/json'>
574 |   <input name='error[good]' value='BOOM!'>
575 |   <input name='error[bad' value='BOOM BOOM!'>
576 | </form>
577 | 
578 | // assuming the user has selected two text files, produces:
579 | {
580 |     "error": {
581 |         "good":   "BOOM!"
582 |     }
583 | ,   "error[bad":  "BOOM BOOM!"
584 | }
585 |
586 | 587 |

2. Conformance

588 |

589 | As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, 590 | and notes in this specification are non-normative. Everything else in this specification is 591 | normative. 592 |

593 |

594 | The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, RECOMMENDED, MAY, 595 | and OPTIONAL in this specification are to be interpreted as described in [RFC2119]. 596 |

597 |
598 | 599 |
600 |

3. Terminology

601 |

602 | The following terms are defined in the HTML specification. [html51] 603 |

604 | 618 |

619 | The following terms are defined in ECMAScript. [ECMA-262] 620 |

621 | 633 |
634 | 635 |
636 |

4. The application/json encoding algorithm

637 |

638 | For the purposes of the algorithms below, an Object corresponds to 639 | the in-memory representation for a JSONObject and an 640 | Array corresponds to the in-memory representation for a 641 | JSONArray. 642 |

643 |

644 | The following algorithm encodes form data as application/json. It operates 645 | on the form data set obtained from constructing the form data set. 646 |

647 |
    648 |
  1. Let resulting object be a new Object.
  2. 649 |
  3. 650 | For each entry in the form data set, perform these substeps: 651 |
      652 |
    1. If the entry's type is file, set the is file flag.
    2. 653 |
    3. 654 | Let steps be the result of running the steps to parse a JSON encoding 655 | path on the entry's name. 656 |
    4. 657 |
    5. Let context be set to the value of resulting object.
    6. 658 |
    7. 659 | For each step in the list of steps, run the following subsubsteps: 660 |
        661 |
      1. 662 | Let the current value be the value obtained by getting the step's key 663 | from the current context. 664 |
      2. 665 |
      3. 666 | Run the steps to set a JSON encoding value with the current 667 | context, the step, the current value, the entry's value, and 668 | the is file flag. 669 |
      4. 670 |
      5. 671 | Update context to be the value returned by the steps to set a JSON 672 | encoding value ran above. 673 |
      6. 674 |
      675 |
    8. 676 |
    677 |
  4. 678 |
  5. 679 | Let result be the value returned from calling the stringify operation 680 | with resulting object as its first parameter and the two remaining parameters 681 | left undefined. 682 |
  6. 683 |
  7. 684 | Encode result as UTF-8 and return the resulting byte stream. 685 |
  8. 686 |
687 |
Note

688 | The algorithm above deliberately ignores any charset information (e.g. from 689 | accept-charset) and always encodes the resulting JSON as UTF-8. This is 690 | an intentionally sane behaviour. 691 |

692 | 693 |

694 | The steps to parse a JSON encoding path are as follows: 695 |

696 |
    697 |
  1. Let path be the path we are to parse.
  2. 698 |
  3. Let original be a copy of var.
  4. 699 |
  5. Let steps be an empty list of steps.
  6. 700 |
  7. 701 | Let first key be the result of 702 | collecting a sequence of characters that 703 | are not U+005B LEFT SQUARE BRACKET ("[") from the path. 704 |
  8. 705 |
  9. 706 | If first key is empty, jump to the step labelled failure below. 707 |
  10. 708 |
  11. 709 | Otherwise remove the collected characters from path and push a step onto 710 | steps with its type set to "object", its key set to the collected characters, 711 | and its last flag unset. 712 |
  12. 713 |
  13. 714 | If the path is empty, set the last flag on the last step in steps 715 | and return steps. 716 |
  14. 717 |
  15. 718 | Loop: 719 | While path is not an empty string, run these substeps: 720 |
      721 |
    1. 722 | If the first two characters in path are U+005B LEFT SQUARE BRACKET ("[") 723 | followed by U+005D RIGHT SQUARE BRACKET ("]"), run these subsubsteps: 724 |
        725 |
      1. Set the append flag on the last step in steps.
      2. 726 |
      3. Remove those two characters from path.
      4. 727 |
      5. 728 | If there are characters left in path, jump to the step labelled 729 | failure below. 730 |
      6. 731 |
      7. Otherwise jump to the step labelled loop above.
      8. 732 |
      733 |
    2. 734 |
    3. 735 | If the first character in path is U+005B LEFT SQUARE BRACKET ("["), 736 | followed by one or more ASCII digits, followed by U+005D RIGHT SQUARE BRACKET 737 | ("]"), run these subsubsteps: 738 |
        739 |
      1. Remove the first character from path.
      2. 740 |
      3. 741 | Collect a sequence of characters being ASCII digits, remove them 742 | from path, and let numeric key be the result of interpreting 743 | them as a base-ten integer. 744 |
      4. 745 |
      5. Remove the following character from path.
      6. 746 |
      7. 747 | Push a step onto steps with its type set to "array", its key set to the 748 | numeric key, and its last flag unset. 749 |
      8. 750 |
      9. Jump to the step labelled loop above.
      10. 751 |
      752 |
    4. 753 |
    5. 754 | If the first character in path is U+005B LEFT SQUARE BRACKET ("["), 755 | followed by one or more characters that are not U+005D RIGHT SQUARE BRACKET, 756 | followed by U+005D RIGHT SQUARE BRACKET ("]"), run these subsubsteps: 757 |
        758 |
      1. Remove the first character from path.
      2. 759 |
      3. 760 | Collect a sequence of characters that are not U+005D RIGHT SQUARE 761 | BRACKET, remove them from path, and let object key be the 762 | result. 763 |
      4. 764 |
      5. Remove the following character from path.
      6. 765 |
      7. 766 | Push a step onto steps with its type set to "object", its key set to 767 | the object key, and its last flag unset. 768 |
      8. 769 |
      9. Jump to the step labelled loop above.
      10. 770 |
      771 |
    6. 772 |
    7. 773 | If this point in the loop is reached, jump to the step labelled failure 774 | below. 775 |
    8. 776 |
    777 |
  16. 778 |
  17. 779 | For each step in steps, run the following substeps: 780 |
      781 |
    1. If the step is the last step, set its last flag.
    2. 782 |
    3. Otherwise, set its next type to the type of the next step in steps.
    4. 783 |
    784 |
  18. 785 |
  19. Return steps.
  20. 786 |
  21. 787 | Failure: return a list of steps containing a single step with its type 788 | set to "object", its key set to original, and its last flag set. 789 |
  22. 790 |
791 | 792 |

793 | The steps to set a JSON encoding value are as follows: 794 |

795 |
    796 |
  1. Let context be the context this algorithm is called with.
  2. 797 |
  3. Let step be the step of the path this algorithm is called with.
  4. 798 |
  5. Let current value be the current value this algorithm is called with.
  6. 799 |
  7. Let entry value be the entry value this algorithm is called with.
  8. 800 |
  9. Let is file be the is file flag this algorithm is called with.
  10. 801 |
  11. 802 | If is file is set then replace entry value with an 803 | Object have its "name" property set to the file's name, its 804 | "type" property set to the file's type, and its "body" property set to the Base64 encoding 805 | of the file's body. [RFC2045] 806 |
  12. 807 |
  13. 808 | If step has its last flag set, run the following substeps: 809 |
      810 |
    1. 811 | If current value is undefined, run the following subsubsteps: 812 |
        813 |
      1. 814 | If step's append flag is set, set the context's property 815 | named by the step's key to a new Array containing 816 | entry value as its only member. 817 |
      2. 818 |
      3. 819 | Otherwise, set the context's property named by the step's 820 | key to entry value. 821 |
      4. 822 |
      823 |
    2. 824 |
    3. 825 | Else if current value is an Array, then get the 826 | context's property named by the step's key and push 827 | entry value onto it. 828 |
    4. 829 |
    5. 830 | Else if current value is an Object and the is 831 | file flag is not set, then run the steps to set a JSON encoding value 832 | with context set to the current value; a step with its type set to 833 | "object", its key set to the empty string, and its last flag set; current value set to 834 | the current value's property named by the empty string; the entry 835 | value; and the is file flag. Return the result. 836 |
    6. 837 |
    7. 838 | Otherwise, set the context's property named by the step's 839 | key to an Array containing current value and 840 | entry value, in this order. 841 |
    8. 842 |
    9. 843 | Return context. 844 |
    10. 845 |
    846 |
  14. 847 |
  15. 848 | Otherwise, run the following substeps: 849 |
      850 |
    1. 851 | If current value is undefined, run the following subsubsteps: 852 |
        853 |
      1. 854 | If step's next type is "array", set the context's property 855 | named by the step's key to a new empty Array and 856 | return it. 857 |
      2. 858 |
      3. 859 | Otherwise,set the context's property named by the step's key 860 | to a new empty Object and return it. 861 |
      4. 862 |
      863 |
    2. 864 |
    3. 865 | Else if current value is an Object, then return 866 | the value of the context's property named by the step's key. 867 |
    4. 868 |
    5. 869 | Else if current value is an Array, then rub the 870 | following subsubsteps: 871 |
        872 |
      1. If step's next type is "array", return current value.
      2. 873 |
      3. 874 | Otherwise, run the following subsubsubsteps: 875 |
          876 |
        1. Let object be a new empty Object.
        2. 877 |
        3. 878 | For each item and zero-based index i in current 879 | value, if item is not undefined then set a 880 | property of object named i to item. 881 |
        4. 882 |
        5. 883 | Otherwise, set the context's property named by the 884 | step's key to object. 885 |
        6. 886 |
        7. Return object.
        8. 887 |
        888 |
      4. 889 |
      890 |
    6. 891 |
    7. 892 | Otherwise, run the following subsubsteps: 893 |
        894 |
      1. 895 | Let object be a new Object with a property named by 896 | the empty string set to current value. 897 |
      2. 898 |
      3. 899 | Set the context's property named by the step's 900 | key to object. 901 |
      4. 902 |
      5. Return object.
      6. 903 |
      904 |
    8. 905 |
    906 |
  16. 907 |
908 |
909 |
910 |

5. Form Submission

911 |

912 | Given that there exist deployed services using JSON and ambient authentication, and given 913 | that form requests are not protected by the same-origin policy by default, if this 914 | encoding were left wide open then a number of attacks would become possible. Because of 915 | this, when using the application/json form encoding the same-origin policy is 916 | enforced. 917 |

918 |

919 | When the 920 | form 921 | submission algorithm is invoked in order to 922 | Submit 923 | as entity with enctype set to application/json and entity 924 | body set to the result of applying the application/json encoding 925 | algorithm, causing the browsing context to 926 | navigate, 927 | the user agent MUST invoke the 928 | fetch 929 | algorithm with the force same-origin flag. 930 |

931 |
932 |
933 |

6. Acknowledgements

934 |

935 | Thanks to Philippe Le Hégaret for serving as a sounding board for the first version of the 936 | encoding algorithm. 937 |

938 | 939 |
940 | 941 | 942 |

A. References

A.1 Normative references

[ECMA-262]
ECMAScript Language Specification, Edition 5.1. June 2011. URL: http://www.ecma-international.org/publications/standards/Ecma-262.htm 943 |
[RFC2045]
N. Freed and N. Borenstein. Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies. November 1996. URL: http://www.ietf.org/rfc/rfc2045.txt 944 |
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Internet RFC 2119. URL: http://www.ietf.org/rfc/rfc2119.txt 945 |
[html51]
Robin Berjon; Steve Faulkner; Travis Leithead; Erika Doyle Navara; Edward O'Connor; Silvia Pfeiffer. HTML 5.1. 4 February 2014. W3C Working Draft. URL: http://www.w3.org/TR/html51/ 946 |
--------------------------------------------------------------------------------