Bertrand
Bertrand

Reputation: 1816

Convert JSON field to ReasonML variant

I have a JSON structure that contains a field period that can either be an object or a string. I already have the variant ready in my code, and it's working fine:

type period = {
  start: string,
  end_: string,
};

type periodVariant =
  | String(string)
  | Period(period);

The problem is when I try to cast the input JSON to the variant type: I simply don't know how to do that. Here's what my attempt looks like:

let periodVariantDecode = (json: Js.Json.t): periodVariant => {
  switch(json) {
  | String(string) => String(Json.Decode.string(string))
  | period => Period(Json.Decode.{
      start: period |> field("start", string),
      end_: period |> field("end", string),
    })
  };
};

Now, of course that doesn't work because I'm trying to match something that is still of type Js.Json.t against String which is part of my periodVariant, but I don't know how to achieve what I want.

Upvotes: 3

Views: 896

Answers (2)

glennsl
glennsl

Reputation: 29106

This is what either is for. Along with map to conveniently "lift" an existing decoder to your variant type.

type period = {
  start: string,
  end_:  string,
};

type periodVariant =
  | String(string)
  | Period(period);

let period = json =>
  Json.Decode.{
    start: json |> field("start", string),
    end_:  json |> field("end", string),
  };

let periodVariantDecode =
  Json.Decode.(either(
    period |> map(p => Period(p)),
    string |> map(s => String(s))
  ));

Upvotes: 6

franky
franky

Reputation: 1333

I see you're using bs-json so one way to do this is to take advantage of the fact that Json.Decode.optional returns None if a decode fails. For your example:

type period = {
  start: string,
  end_: string,
};

type periodVariant =
  | String(string)
  | Period(period);

let periodVariantDecode = json => {
  let periodString = json |> Json.Decode.(optional(string));
  switch (periodString) {
  | Some(periodString) => String(periodString)
  | None =>
    let periodObj =
      Json.Decode.{
        start: json |> field("start", string),
        end_: json |> field("end", string),
      };
    Period(periodObj);
  };
};

That should compile with periodVariantDecode being of type Js.Json.t => periodVariant. I'm not sure if it's the idiomatic way to go though!

Upvotes: 2

Related Questions