1 of 16

JSON.parse source text access

2 of 16

Background

3 of 16

JSON.parse is lossy, even with a reviver function

// Numbers are subject to IEEE 754 precision limits.�JSON.parse(" 9999999999999999")�// → 10000000000000000��// …and reviver functions receive already-parsed values.�JSON.parse(" 9999999999999999", (key, val) => BigInt(val))�// → 10000000000000000n

4 of 16

Reviver functions lack context

// Conditional processing must bubble up to a differentiating level�JSON.parse(`{ "metadata": { "date": "2018-09-25T10:00-04:00" },"value": { "date": "2018-09-25T10:00-04:00" } }`,� function(key, val) {� if ( key === "value" && "date" in val ) {� val.date = new Date(val.date);� }� return val;� }�)

// → { metadata: { date: "2018-09-25T10:00-04:00" },� value: { date: new Date("2018-09-25T14:00:00.000Z") } }

5 of 16

Proposal

6 of 16

Expose source text to reviver functions

// Numbers are still subject to IEEE 754 precision limits.�JSON.parse(" 9999999999999999")�// → 10000000000000000��// …but reviver functions gain access to the raw source.�JSON.parse(" 9999999999999999", (key, val, {source}) => BigInt(source))�// → 9999999999999999n

7 of 16

Potential enhancement: Supply parent keys

// Conditional processing can take place directly at a deep value.�JSON.parse(`{ "metadata": { "date": "2018-09-25T10:00-04:00" },"value": { "date": "2018-09-25T10:00-04:00" } }`,� (key, val, {keys}) =>� keys.length === 3 && keys.join(".") === ".value.date" ?new Date(val) :� val�)

// → { metadata: { date: "2018-09-25T10:00-04:00" },� value: { date: new Date("2018-09-25T14:00:00.000Z") } }

8 of 16

Discussion

9 of 16

Draft spec text

1.1 Runtime Semantics: InternalizeJSONProperty ( holder, name, reviver, parseNode )

The abstract operation InternalizeJSONProperty is a recursive abstract operation that takes twofour parameters: a holder object and, the String name of a property in that object, a reviver function, and a parseNode Parse Node corresponding with the original value of the property. InternalizeJSONProperty uses the value of reviver that was originally passed to the above parse function.

10 of 16

Draft spec text

  1. Let context be ! ObjectCreate(%ObjectPrototype%).
  2. Let sourceText be the span of source text matched by parseNode.
  3. Perform ! CreateDataPropertyOrThrow(context, "source", ! UTF16Encode(sourceText)).

11 of 16

Draft spec text

  1. If isArray is true, then
    1. Let I be 0.
    2. Let len be ? LengthOfArrayLike(val).
    3. Repeat, while I < len,
      1. Let child be the AssignmentExpression descendant of parseNode corresponding with the element of val at index I.
      2. Let newElement be ? InternalizeJSONProperty(val, ! ToString(I), reviver, child).

12 of 16

Draft spec text

  1. Else [If isArray is not true],
    1. Let keys be ? EnumerableOwnPropertyNames(val, key).
    2. For each String P in keys, do
      1. Let child be the AssignmentExpression descendant of parseNode responsible for the value of the property of val with name P.
      2. NOTE: In the case of JSON text specifying multiple name/value pairs with the same name for a single object (such as {"a":"lost","a":"kept"}), the value for the corresponding property of the resulting ECMAScript object is specified by the last pair with that name.
      3. Let newElement be ? InternalizeJSONProperty(val, P, reviver, child).

13 of 16

Other questions

14 of 16

Other questions

  • Expose parse node indices?
  • Support parent keys?
  • Extend to include serialization?

15 of 16

Next steps

Questions?

Feedback?

Advance to Stage 2?

16 of 16

What is Stage 2?

Stage

Purpose

Entrance Criteria

Acceptance Signifies

Spec Quality

Post-Acceptance Changes Expected

Implementation Types Expected*

2

Draft

Precisely describe the syntax and semantics using formal spec language

Initial spec text

The committee expects the feature to be developed and eventually included in the standard

Draft: all major semantics, syntax and API are covered, but TODOs, placeholders and editorial issues are expected

Incremental

Experimental