|
| 1 | +# Title of the feature |
| 2 | + |
| 3 | +- JEP: (leave blank) |
| 4 | +- Author: tmccombs |
| 5 | +- Created: 2023-05-30 |
| 6 | + |
| 7 | +## Abstract |
| 8 | +[abstract]: #abstract |
| 9 | + |
| 10 | +Add two new functions which allow parsing a string into a JSON value, and serializing a value to a JSON string. |
| 11 | + |
| 12 | +## Motivation |
| 13 | +[motivation]: #motivation |
| 14 | + |
| 15 | +JSON values sometimes contain strings, which are themselves JSON. As a specific example, some AWS APIs |
| 16 | +return responses that include values which are strings containing JSON strings, such as IAM policy |
| 17 | +documents. |
| 18 | + |
| 19 | +Previously, it was not possible to extract data from such a string directly with JMESPath. At least without |
| 20 | +having to use one pass to extract the string value, then use a different expression the result of parsing the |
| 21 | +JSON from that string. |
| 22 | + |
| 23 | +Conversely, if you need to serialize data into a string field of the result, then if your input is not a string |
| 24 | +you can currently use `to_string`, but that function won't correctly serialize an existing string as a JSON string. |
| 25 | +If you don't know the actual type of the value, and it could be a string, you can't safely serialize as a JSON string. |
| 26 | + |
| 27 | +## Specification |
| 28 | +[specification]: #specification |
| 29 | + |
| 30 | +This JEP defines two new functions `from_json` and `to_json`. |
| 31 | + |
| 32 | +### `from_json` |
| 33 | + |
| 34 | +The `from_json` function has the following signature: |
| 35 | + |
| 36 | +``` |
| 37 | +any from_json(string $json_data) |
| 38 | +``` |
| 39 | + |
| 40 | +This function takes a string, and will parse it as a JSON document and return the resulting value. The resulting |
| 41 | +value may be any JSON type. |
| 42 | + |
| 43 | +If the input string is not a valid JSON document, an `invalid-value` error is raised. |
| 44 | + |
| 45 | +### `to_json` |
| 46 | + |
| 47 | +The `to_json` function has the following signature: |
| 48 | + |
| 49 | +``` |
| 50 | +string to_json(any $value) |
| 51 | +``` |
| 52 | + |
| 53 | +This function performs the inverse of `from_json`. It takes any JSON value as input, and serialize the result |
| 54 | +as a JSON document, and then returns a string containing that serialized result. The resulting JSON should not |
| 55 | +contain any unneeded whitespace, including newlines, spaces, or tabs. |
| 56 | + |
| 57 | +## Rationale |
| 58 | +[rationale]: #rationale |
| 59 | + |
| 60 | +The names `from_json` and `to_json` were chosen because the names are symmetric with each other and are similar to |
| 61 | +the names of the corresponding functions in [`jq`](https://jqlang.github.io/jq/manual/#Convertto/fromJSON). |
| 62 | + |
| 63 | +However, other names could also be accptable, such as: |
| 64 | +* `parse_json`/`stringify_json` |
| 65 | +* `json_parse`/`json_stringify` |
| 66 | +* `deserialize`/`serialize` |
| 67 | + |
| 68 | +Another alternative is to add the `from_json` function but not the `to_json` function, since the existing |
| 69 | +`to_string` function can already serialize most values as json. However, the fact that it can't serialize a string as to |
| 70 | +JSON is an unfortunate gap, and it seems odd not to have symmetric functions going in both directions. If only a deserizalization |
| 71 | +function is added, `from_string` may also be an appropriate name. |
| 72 | + |
| 73 | +This specification only supports serializing as minimal JSON without whitespace. This is conjectured to be what is desired in most |
| 74 | +cases where the `to_json` function is used, in order to keep the size of the resulting data small. This is also consistent |
| 75 | +with the existing `to_string` function. However, a future extension could |
| 76 | +provide functionality to output JSON in a "pretty" format including newlines and indentation, either as an additional function, or with |
| 77 | +an optional argument to the `to_json` function. However, this was intentionally left out of scope for this proposal to keep the scope small. |
| 78 | + |
| 79 | +For handlinge invalid JSON input, another possibility is that `from_json` could return some kind of value. However, it would be difficult to distinguish |
| 80 | +between parsing a legitimate value, and inability to parse the string. And in most use cases, invalid JSON is probably an unexpected scenario that should |
| 81 | +be treated as an error. |
| 82 | + |
| 83 | +## Testcases |
| 84 | +[testcases]: #testcases |
| 85 | + |
| 86 | +```yaml |
| 87 | +given: |
| 88 | + string: "abc" |
| 89 | + empty_list: [] |
| 90 | + empty_hash: {} |
| 91 | + object: |
| 92 | + foo: "bar" |
| 93 | + bar: "baz" |
| 94 | + 'null': null |
| 95 | + json: | |
| 96 | + { |
| 97 | + "str": "string", |
| 98 | + "num": 1.25e4, |
| 99 | + "arr": [1,2, "a"], |
| 100 | + "nothing": null |
| 101 | + } |
| 102 | +cases: |
| 103 | + - expression: from_json(json) |
| 104 | + result: |
| 105 | + str: "string" |
| 106 | + num: 1.25e4 |
| 107 | + arr: [1,2, "a"] |
| 108 | + nothing: null |
| 109 | + - expression: from_json(json).str |
| 110 | + result: "string" |
| 111 | + - expression: "from_json('true')" |
| 112 | + result: true |
| 113 | + - expression: "from_json('false')" |
| 114 | + result: false |
| 115 | + - expression: "from_json('123')" |
| 116 | + result: 123 |
| 117 | + - expression: "from_json('1.5')" |
| 118 | + result: 1.5 |
| 119 | + - expression: "from_json('1e8')" |
| 120 | + result: 1e8 |
| 121 | + - expression: "from_json('5e-3')" |
| 122 | + result: .005 |
| 123 | + - expression: "from_json('\"abc\"')" |
| 124 | + result: "abc" |
| 125 | + - expression: "from_json('[]'" |
| 126 | + result: [] |
| 127 | + - expression: "from_json('{}')" |
| 128 | + result: {} |
| 129 | + - expression: "from_json('null')" |
| 130 | + result: null |
| 131 | + - expression: "from_json('abc')" |
| 132 | + error: invalid-value |
| 133 | + - expression: to_json(object) |
| 134 | + result: '{"foo":"bar","bar":"baz"}' |
| 135 | + - expression: to_json(string) |
| 136 | + result: '"abc"' |
| 137 | + - expression: to_json(empty_list) |
| 138 | + result: [] |
| 139 | + - expression: to_json(empty_hash) |
| 140 | + result: {} |
| 141 | +``` |
0 commit comments