We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies.

We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies. Less

We use cookies and other tracking technologies... More

Login or register
to publish this job!

Login or register
to save this job!

Login or register
to save interesting jobs!

Login or register
to get access to all your job applications!

Login or register to start contributing with an article!

Login or register
to see more jobs from this company!

Login or register
to boost this post!

Show some love to the author of this blog by giving their post some rocket fuel ๐Ÿš€.

Login or register to search for your ideal job!

Login or register to start working on this issue!

Login or register
to save articles!

Login to see the application

Engineers who find a new job through WorksHub average a 15% increase in salary ๐Ÿš€

You will be redirected back to this page right after signin

Blog hero image

Parsing JSON in ReScript Part II: Building Blocks

Eleanor Holley 8 March, 2021 | 2 min read

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, 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 uses option types, but I want Results with error messages.

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

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.

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.

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.

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.

Originally published on webbureaucrat.gitlab.io

Author's avatar
Eleanor Holley
an enthusiastic functional programmer.

Related Issues

Concordium / Testnet3-Challenges
  • Open
  • 0
  • 0
  • Intermediate
    Concordium / Testnet3-Challenges
    Concordium / Testnet3-Challenges
    • Open
    • 0
    • 0
    • Intermediate
      Concordium / Testnet3-Challenges
      Concordium / Testnet3-Challenges
      • Open
      • 0
      • 0
      • Intermediate
        Concordium / Testnet3-Challenges
        Concordium / Testnet3-Challenges
        • Open
        • 0
        • 0
        • Intermediate

          Get hired!

          Sign up now and apply for roles at companies that interest you.

          Engineers who find a new job through WorksHub average a 15% increase in salary.

          Start with GitHubStart with Stack OverflowStart with Email