Ocaml - Json Encoding and Decoding

Posted on November 26, 2018

I usually have trouble serializing/deserializing json when I first learn a staticly typed language. I had the same experience with C# and in Haskell. Luckily, there’s a lot of resources and it’s easy to understand. This time I’m learning Ocaml/Bucklescript and I’m encoutering the same thing. Good thing bucklescript’s standard library provides Js.Json.

JSON Encoding

Creating a simple json object.

let jsonPerson =
  [ ("firstName", Js.Json.string "John")
  ; ("lastName", Js.Json.string "Doe")
  ]
  |> Js.Dict.fromList
  |> Js.Json.object_

Creating json from a record. First, create the record type.

type person = 
  { firstName : string
  ; lastName : string
  }

Then create a record value.

let personRec = 
  { firstName = "John"
  ; lastName = "Doe"
  }

Now, a function that will serialize the record.

let jsonPerson (p : person) = 
[ ("firstName", Js.Json.string p.firstName)
; ("lastName", Js.Json.string p.lastName)
]
|> Js.Dict.fromList
|> Js.Json.object_

To see the output in the console.

let _ = 
  personRec 
  |> jsonPerson 
  |> Js.log

JSON Decoding

I’m gonna do the other way around in this next section. Deserializing json! When I get a json object I will transform it into personRec record.

Let’s say this is the json object I get.

{ firstName : "John"
, lastName : "Doe"
}

First, I need to define a record since there’s no such thing as anonymous records in Ocaml.

type person = 
{ firstName : string
; lastName : string
} [@@bs.deriving abstract]

I’ll use [@@bs.deriving abstract] decoractor for maninpulating json since Ocaml’s data representation is not really the same with javascript, and also it will give me convenience functions to access the fields.

I’ll create a utility function to help me extract json.

let extractField field jsonDict =
  let open Belt_Option in
  let id a = a in
    flatMap
      (Js.Dict.get jsonDict field)
      Js.Json.decodeString
    |> (function o -> mapWithDefault o "" id)

This will be my main decoding function

let jsonToPerson json =
  match Js.Json.decodeObject json with
  | Some j ->
    personRec
      ~firstName:(extractField "firstName" j)
      ~lastName:(extractField "lastName" j)
  | None ->
    personRec
      ~firstName:""
      ~lastName:""

To simulate a json object I’ll make jsonPerosn.

let jsonPerson =
  [ ("firstName", Js.Json.string "John")
  ; ("lastName", Js.Json.string "Doe")
  ]
  |> Js.Dict.fromList
  |> Js.Json.object_

To view the output

let _ = 
  person 
  |> jsonToPerson 
  |> Js.log

Using bs-json

JSON Encoding

I’ll encode a record using bs-json to turn it into a json object. First, I’ll create the record type.

type person = 
{ firstName : string
; lastName : string
}

Then, the record itself.

let personRec =
{ firstName = "John"
; lastName = "Doe"
}

Next, will be the function to create the json object from the record

let jsonPerson (p : person) =
  let open Json.Encode in
    object_
      [ ("firstName", string p.firstName)
      ; ("lastName", string p.lastName)
      ]

To view the output in the console.

let _ = 
  personRec 
  |> jsonPerson 
  |> Js.log

JSON Decoding

I’m expecting the same json from above.

{ firstName : "John"
, lastName : "Doe"
}

Define the person record.

type person =
{ firstName : string
; lastName : string
} [@@bs.deriving abstract]

Then, the function to transform the record to a json object.

let jsonToPerson json =
  let open Json.Decode in
    person
    ~firstName:(json |> (field "firstName" string))
    ~lastName:(json |> (field "lastName" string))

To see the output in the console

let _ =
  jsonPerson
  |> jsonToPerson
  |> Js.log