Playing With Cohttp and Yojson

2020-06-21

Playing with Cohttp and Yojson

Few days ago I needed to parse a json file. This file can be found in a github repository. The idea is to try to download the very last version, if the download fails, a local copy is used.

The libraries

Cohttp

It is an OCaml library for HTTP clients and servers using Lwt and Async

Yojson

It is a Low-level JSON parsing and pretty-printing library for OCaml

Example

The context, is that I wanted to be able to get the OS version and Linux distribution of a running system. I decided to mimic the JavaScript getos project.

Here is the organisation of the project:

.
├── bin
│   ├── display_os.ml
│   └── dune
├── data
│   └── os.json
├── dune-project
├── getos.opam
└── lib
    ├── dune
    └── getos.ml
  • lib/getos.ml
open Cohttp
open Cohttp_lwt_unix
module YojsonB = Yojson.Basic
module YojsonBU = Yojson.Basic.Util

let os_json_uri = "https://raw.githubusercontent.com/retrohacker/getos/master/os.json"

type os = {
  os: string;
  dist: string;
  codename: string;
  release: string;
}

The following part is simple, first it tries to download the json file. If it succeeds, the body is transformed into a string that Yojson will turn into a Yojson.t type with Yojson.Basic.from_string.

If the Http Client fails to download the file, a Yojson.t element is generated from a local copy with Yojson.Basic.from_file.

(** Download the last os.json reference
    If the download fails, use the one in data/os.json *)
let get_os_json () =
  let%lwt (resp, body) = Client.get (Uri.of_string os_json_uri) in
  let code = resp |> Response.status |> Code.code_of_status in
  let os_json =
    if code = 200 then
      let%lwt json = Cohttp_lwt.Body.to_string body in Lwt.return @@ YojsonB.from_string json
    else let () = print_endline "Download of os.json failed, using the packaged one." in
      Lwt.return @@ YojsonB.from_file "data/os.json"
  in os_json

Now that we have a Yojson.t type element, it is time to manipulate it in order to print or access data:

  • Yojson.Basic.Util.keys: it returns a list of strings that are the keys of a json object.
  • Yojson.Basic.Util.member: it returns the value of a Yojson.t element that correspond to a specific key.
  • Yojson.Basic.Util.to_list: if a Yojson.t element is an array, it returns a list of Yojson.t elements.
  • Yojson.Basic.Util.to_string: it returns a string from a Yojson.t element.
let getLinuxDistro () =
  let%lwt os_json = get_os_json () in
  (*
   * For each files which are registred as key in the main json data
   * make fs.stat to check if it exists
   * if it exists get the array of possible distributions
   * for each possible distributions, try to match the content of the
   * file with some predefined rules defined here:
   * https://github.com/retrohacker/getos/tree/master/logic
   * how to format those rules in OCaml ?
   * *)
  let keys = YojsonBU.keys os_json in
  let%lwt file = Lwt_list.find_s Lwt_unix.file_exists keys in
  let () = print_endline file in
  let distribs = YojsonBU.member file os_json |> YojsonBU.to_list |> List.map YojsonBU.to_string in
  let () = List.iter print_endline distribs in
  Lwt.return {os = "Linux"; dist = ""; codename = ""; release = ""}

let infos () =
  if ( Sys.os_type <> "Unix" ) then Lwt.return {os = Sys.os_type; dist = ""; codename = ""; release = ""}
  else getLinuxDistro ()
developmentnotesOCamlFunctional ProgrammingHttpJson

Using Lwt_ppx Rewriter

My First Post