-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add JsonExtractor utility class (#19)
The extractor wraps standard boilerplate for working with JsonType structures into a utility class that makes for easier reading code when pulling values of known types from a parsed Json structure.
- Loading branch information
1 parent
f6a18ef
commit 7ba00a5
Showing
3 changed files
with
285 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
## Add JsonExtractor utility class | ||
|
||
We've added a new class to the `JSON` library. `JsonExtractor` bundles up a lot of the boilerplate needed to extract typed values from Json objects. | ||
|
||
Given the following Json: | ||
|
||
```json | ||
{ | ||
"name": "John", | ||
"age": 30, | ||
"isStudent": true | ||
} | ||
``` | ||
|
||
Where you previously had to do: | ||
|
||
```pony | ||
let doc = recover val JsonDoc.>parse(src)? end | ||
let name = (doc.data as JsonOject).data("name")? as String | ||
let age = (doc.data as JsonOject).data("age")? as I64 | ||
let isStudent = (doc.data as JsonOject).data("isStudent")? as Bool | ||
``` | ||
|
||
You can now do: | ||
|
||
```pony | ||
let doc = recover val JsonDoc.>parse(src)? end | ||
let name = JsonExtractor(doc.data)("name")?.as_string()? | ||
let age = JsonExtractor(doc.data)("age")?.as_i64()? | ||
let isStudent = JsonExtractor(doc.data)("isStudent")?.as_bool()? | ||
``` | ||
|
||
For simple Json structures such as the one above, there is little difference. However, once you start dealing with nested objects and arrays, `JsonExtractor` can save you a lot of boilerplate code. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
use "collections" | ||
|
||
class val JsonExtractor | ||
""" | ||
Utility class for working with JSON structures. | ||
Given the following JSON: | ||
```json | ||
{ | ||
"environments": [ | ||
{ | ||
"name": "corral-env", | ||
"user": "sean", | ||
"image": "an-image:latest", | ||
"shell": "fish", | ||
"workdir": "/workspace", | ||
"workspace": "/home/sean/ponylang/corral", | ||
"mounts": [ | ||
{"source":"/var/run/docker.sock","target":"/var/run/docker.sock", "type": "bind"} | ||
] | ||
} | ||
] | ||
} | ||
``` | ||
We can use the following code to extract our values: | ||
```pony | ||
primitive Parser | ||
fun apply(json: JsonType val): Array[Environment] val ? => | ||
recover val | ||
let envs = JsonExtractor(json)("environments")?.as_array()? | ||
let result: Array[Environment] = Array[Environment] | ||
for e in envs.values() do | ||
let obj = JsonExtractor(e).as_object()? | ||
let name = JsonExtractor(obj("name")?).as_string()? | ||
let user = JsonExtractor(obj("user")?).as_string()? | ||
let image = JsonExtractor(obj("image")?).as_string()? | ||
let shell = JsonExtractor(obj("shell")?).as_string()? | ||
let workdir = JsonExtractor(obj("workdir")?).as_string()? | ||
let workspace = JsonExtractor(obj("workspace")?).as_string()? | ||
let mounts = recover trn Array[Mount] end | ||
for i in JsonExtractor(obj("mounts")?).as_array()?.values() do | ||
let m = MountParser(i)? | ||
mounts.push(m) | ||
end | ||
let environment = Environment(name, user, image, shell, workdir, workspace, consume mounts) | ||
result.push(environment) | ||
end | ||
result | ||
end | ||
primitive MountParser | ||
fun apply(json: JsonType val): Mount ? => | ||
let obj = JsonExtractor(json).as_object()? | ||
let source = JsonExtractor(obj("source")?).as_string()? | ||
let target = JsonExtractor(obj("target")?).as_string()? | ||
let mtype = JsonExtractor(obj("type")?).as_string()? | ||
Mount(source, target, mtype) | ||
``` | ||
The JsonExtractor creates a lot of intermediate objects, but it makes the code | ||
easier to read and understand. We suggest not using it in critical paths where | ||
performance is a concern. | ||
""" | ||
let _json: JsonType val | ||
|
||
new val create(json: JsonType val) => | ||
""" | ||
Create a new JsonExtractor from a JSON structure. | ||
""" | ||
_json = json | ||
|
||
fun val apply(idx_or_key: (String | USize)): JsonExtractor val ? => | ||
""" | ||
Extract an array or object by index or key and return a new JsonExtractor. | ||
""" | ||
match (_json, idx_or_key) | ||
| (let a: JsonArray val, let idx: USize) => | ||
JsonExtractor((a.data)(idx)?) | ||
| (let o: JsonObject val, let key: String) => | ||
JsonExtractor((o.data)(key)?) | ||
else | ||
error | ||
end | ||
|
||
fun val size(): USize ? => | ||
""" | ||
Return the size of the JSON structure. | ||
Results in an error for any structure that isn't a `JsonArray` or `JsonObject`. | ||
""" | ||
match _json | ||
| let a: JsonArray val => | ||
a.data.size() | ||
| let o: JsonObject val => | ||
o.data.size() | ||
else | ||
error | ||
end | ||
|
||
fun val values(): Iterator[JsonType val] ? => | ||
""" | ||
Return an iterator over the values of the JSON structure. | ||
Results in an error for any structure that isn't a `JsonArray`. | ||
""" | ||
match _json | ||
| let a: JsonArray val => | ||
a.data.values() | ||
else | ||
error | ||
end | ||
|
||
fun val pairs(): Iterator[(String, JsonType val)] ? => | ||
""" | ||
Return a pairs iterator over the values of the JSON structure. | ||
Results in an error for any structure that isn't a `JsonArray`. | ||
""" | ||
match _json | ||
| let o: JsonObject val => | ||
o.data.pairs() | ||
else | ||
error | ||
end | ||
|
||
fun val as_array(): Array[JsonType] val ? => | ||
""" | ||
Extract an Array from the JSON structure. | ||
""" | ||
match _json | ||
| let a: JsonArray val => | ||
a.data | ||
else | ||
error | ||
end | ||
|
||
fun val as_object(): Map[String, JsonType] val ? => | ||
""" | ||
Extract a Map from the JSON structure. | ||
""" | ||
match _json | ||
| let o: JsonObject val => | ||
o.data | ||
else | ||
error | ||
end | ||
|
||
fun val as_string(): String ? => | ||
""" | ||
Extract a String from the JSON structure. | ||
""" | ||
_json as String | ||
|
||
fun val as_none(): None ? => | ||
""" | ||
Extract a None from the JSON structure. | ||
""" | ||
_json as None | ||
|
||
fun val as_f64(): F64 ? => | ||
""" | ||
Extract a F64 from the JSON structure. | ||
""" | ||
_json as F64 | ||
|
||
fun val as_i64(): I64 ? => | ||
""" | ||
Extract a I64 from the JSON structure. | ||
""" | ||
_json as I64 | ||
|
||
fun val as_bool(): Bool ? => | ||
""" | ||
Extract a Bool from the JSON structure. | ||
""" | ||
_json as Bool | ||
|
||
fun val as_string_or_none(): (String | None) ? => | ||
""" | ||
Extract a String or None from the JSON structure. | ||
""" | ||
match _json | ||
| let s: String => s | ||
| let n: None => n | ||
else | ||
error | ||
end |