Parsing JSON in ReScript Part II: Building Blocks
Tags: bucklescript, functional, reason, rescript
Published: 2020-11-18 16:53:09

---
title: Parsing JSON in ReScript Part II: Building Blocks
published: true
date: 2020-11-15 00:00:00 UTC
tags: ["bucklescript", "functional", "reason", "rescript"]
canonical_url: https://webbureaucrat.gitlab.io/posts/parsing-json-in-rescript-part-ii-building-blocks/
---

This is the second in a series of articles on how to build one's own, general-purpose parsing library. After having established a few expectations in the [previous post](https://webbureaucrat.gitlab.io/posts/parsing-json-in-rescript-part-i-prerequisites-and-requirements/), we are ready to begin building our utilities for our library. Let's start with some highly generalized utilities for functional programming.

### Basic Functional Utilities

The first two functions here are utilities to go back and forth between `Result<'a, string>` and `option` types. This is important because [`Js.Json`](https://rescript-lang.org/docs/manual/latest/api/js/json) uses `option` types, but I want `Result`s with error messages.

The other is a utility that takes two `Result`s and a function that takes two parameters and applies the function to the contents of both `Result`s if and only if both `Result`s are `Ok`. Bluntly, it's `map` but with two items, so it's `mapTogether`.

```ocaml
open Belt;

/* general utilities */

let toOption = (result: Result.t<'t, 'e>): option<'t> => 
    switch result { 
    | Error(_) => None; 
    | Ok(t) => Some(t)
};

let toResult = (op: option<'a>, err: 'b): Result.t<'a, 'b> => 
    switch op { 
    | None => Result.Error(err); 
    | Some(x) => Result.Ok(x);
};

let mapTogether = (first: Result.t<'a, 'error>, 
                   second: Result.t<'b, 'error>, 
                   func: ('a, 'b) => 'c): Result.t<'c, 'error> =>
    Result.flatMap(first, f => Result.map(second, s => func(f, s)));
```

Neither of these have a lot to do with parsers specifically, but I want them on hand. Let's start to take things out of abstraction.

### Parsing Utilities

Finally, we can get started on some parsing-specific code. Specifically, I want my parsing library to have error messages, so I need a couple of functions to help generate consistent and descriptive failure strings.

The first is `getProp`, which is ust a wrapper around `Js.Dict.get` that uses our above `toResult`. It takes a dictionary and a property name, and if the given dictionary doesn't have an entry for the property name, it will fail with an error message that tells us which property failed. If it succeeds, it will give us a ReScript JSON type, which we can then narrow down to our expected type.

```ocaml
let getProp = (dict: Js.Dict.t<Js.Json.t>, prop: string): 
Result.t<Js.Json.t, string> => 
    Js.Dict.get(dict, prop) 
    -> toResult(Js.String.concat("Parse Error: property not found: ", prop));
```

The second is a function that helps us generate descriptive errors. This function will be called if `getProp` succeeds with a JSON, but that JSON can't be resolved as the type we expect. All it does is generate an error like "Parse Error: name not string." or some other combination.

```ocaml
let typeError = (type_: string, prop: string): string => 
    "Parse Error: "
    |> Js.String.concat(prop)
    |> Js.String.concat(" not ")
    |> Js.String.concat(type_);
```

Lastly, I want one more utility for dealing with more problematic numbers. Technically, `NaN` behaves as a number a lot of the time, but it's also, quite explicitly, well, _not a number_. I want the option to handle these as failures instead of successes, so I'm going to write a quick filter to turn these fake successes into descriptive failures.

```ocaml
let failNaN = (number: float): Result.t<float, string> => { 
    if Js.Float.isNaN(number) { 
        Result.Error("Parse Error: yielded NaN") 
    } else { 
        Result.Ok(number) 
    }
};
```

### In conclusion

This has been a walk through of a couple of helpful utilities for building parsers. With these out of the way, we finally [start building our parsing library](https://webbureaucrat.gitlab.io/posts/parsing-json-in-rescript-part-iii-getting-to-the-point/).