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 ()