polar

Working with JSON

Std.Json turns Polar values into JSON text and back. Decoding checks the shape of the data, so a decoded value really has the type you asked for.

Make a type encodable

A type can be encoded once it has the Json trait. For your own types, derive it:

uses
  Std.Json
  Std.List
  Std.Option
  Std.Result

types
  Priority = Low | High derive(Eq, Json)

  Note = {
    id: Int,
    text: String,
    priority: Priority,
    tags: List<String>,
    due: Option<String>,
  } derive(Json)

Every field type needs Json too. The basic types, List, Option and Result already have it, and Priority derives it.

Encode

Json.encode turns a value into a string:

let note: Note = {
  id: 1,
  text: "Buy milk",
  priority: High,
  tags: ["home"],
  due: None,
}

Log.info(Json.encode(note))
{"id":1,"text":"Buy milk","priority":{"$":"High"},"tags":["home"],"due":null}

Records become objects, lists become arrays, and None becomes null. Variant cases become an object with a "$" key naming the case.

Note: The let note: Note annotation matters here. Without it, the literal is a plain record, not a Note, and has no Json implementation. See Records.

Decode

Json.decode goes the other way. Decoding can fail, so it returns a Result. Tell Polar which type you expect by annotating the result:

let good: Result<JsonError, Note> = Json.decode(
  "{\"id\": 2, \"text\": \"Call Bob\", \"priority\": {\"$\": \"Low\"}, \"tags\": [], \"due\": \"friday\"}",
)

match good {
  Ok(n) -> Log.info("decoded #{n.text}, due #{Option.with_default(n.due, "whenever")}"),
  Err(e) -> Log.info(Json.message(e)),
}
decoded Call Bob, due friday

Decode errors

When the data doesn't fit the type, you get a JsonError saying where and why. Json.message turns it into readable text:

let bad: Result<JsonError, Note> = Json.decode("{\"id\": \"two\"}")
.id: expected Int, found a string

A JsonError is a record with path, expected and found fields, if you want to build your own message.

Tip: Decode at the edges of your program, where data arrives from a request, a file or another service. After that, the rest of your code works with typed values and never needs to check them again.

Where else JSON is used

Bridges send values between browser and server as JSON, so the types they carry need Json. The example web framework's Response.json uses it to build API responses.