jsonreasonbucklescriptrescript

How can one iterate/map over a Js.Json.t that is an array?


I'm trying to decode a JSON array that has the type Js.Json.t (not array(Js.Json.t) apparently). A call to Js.log(jsonList) reveals that it is an array, but I'm not certain how to map over the elements in the array to decode it.

So far, I've got:

let json_to_list = response => {
    switch (response |> Js.Json.decodeObject) {
        | None => {
            Js.log("Decoding JSON failed!!")
            None
        }
        | Some(jsonObject) => {
            switch (jsonObject -> Js.Dict.get("list")) {
                | None => {
                    Js.log("JSON didn't have a 'list' key/value.")
                    None
                }
                | Some(jsonList) => {
                    jsonList
                    |> Js.List.map(
                            /*  compiler is expecting an uncurried function */
                            record => {
                            switch (record->Js.Dict.get("session-id") { /* ... */ }
                        }
                    )
                }
            }
        }
    }
};

The compiler is expecting an uncurried function, which, I don't know how to provide.

EDIT

I'd like to think I'm closer, but I'm getting an This has type: array(unit) Somewhere wanted: unit on the line (below) value |> Array.map(Js.log)

let json_to_list = response => {
    Js.log("Decoding JSON")
    switch (response |> Js.Json.decodeObject) {
        | None => {
            Js.log("Decoding JSON failed!!")
            None
        }
        | Some(jsonObject) => {
            switch (jsonObject -> Js.Dict.get("list")) {
                | None => {
                    Js.log("JSON didn't have a 'list' key/value.")
                    None
                }
                | Some(jsonArray) => switch (Js.Json.decodeArray(jsonArray)) {
                    | None => {
                        Js.log("JSON Object wasn't an array.")
                        None
                    }
                    | Some(value) => {
                        Js.log("Value length: " ++ string_of_int(value|>Js.Array.length))
                        value |> Array.map(Js.log)
                        Some(value)
                    }
                }
            }
        }
    }
};

Solution

  • There are several ways of doing this, depending on what you know about the data at compile-time and what you actually need.

    If you know exactly what it is, and there's no chance you could receive anything else, you could just cast it into the type you want without doing any checks at runtime:

    external toMyType: Js.Json.t => array(something) = "%identity"
    
    let myData = toMyType(json)
    

    If you don't know the shape of the data until runtime, you can use Js.Json.classify:

    let decodeArrayItem = ...
    
    let myData : array(something) =
      switch Js.Json.classify(json) {
      | Js.Json.JSONArray(array) => Array.map(decodeArrayItem, array)
      | _ => []
      }
    }
    

    Or, if you could get anything but arrays are all you care about, you can use Js.Json.,decodeArray as a short-hand, which returns an option you can deal with further:

    let decodeArrayItem = ...
    
    let maybeData : option(array(something)) =
      Js.Json.decodeArray(json)
      |> Option.map(Array.map(decodeArrayItem))
    

    Lastly, my recommended option for most scenarios is to use one of the third-party JSON decoder libraries, which tends to be designed for composition and therefore much more convenient for decoding large data structures. For example, using @glennsll/bs-json (no bias here, obviously):

    module Decode = {
      let arrayItem = ...
    
      let myData =
        Json.Decode.array(arrayItem)
    }
    
    let myData =
      try Decode.myData(json) catch {
      | Js.Json.DecodeError(_) => []
      }
    

    Edit: As for the actual error you get, you can turn a curried anonymus function into an uncurried one just by using a slightly different syntax:

    let curried = record => ...
    let uncurried = (. record) => ...