With ReScript 11, what is the recommended wayt to serialize/deserialize JSON

I am looking at this article here Better interop with customizable variants | ReScript Blog on the section where it shows an example of writing your own JSON serializer/deserializer code.

On the other hand, I am also aware of https://github.com/rescript-association/rescript-core/blob/main/src/Core__JSON.res

I might be confusing myself, but officially what is the recommended way, starting from ReScript 11, to serialize and de-serialize JSON?

It depends how much you control the JSON input. The technique from the article is great if you are certain the input will be in the correct shape, but it lacks error messages should validation fail (it just returns None).

There are a few community libraries for managing JSON, but I don’t know if any of them are officially recommended so I won’t link to them (yet).

This is not JSON “support”, it’s just a binding to the JavaScript JSON api. It can be used to implement JSON serialisation and deserialisation, and many of the community efforts rely on it.

It depends how much you control the JSON input. The technique from the article is great if you are certain the input will be in the correct shape, but it lacks error messages should validation fail (it just returns None).

Thanks, I think it can easily be modified to return result instead of option.

This is not JSON “support”, it’s just a binding to the JavaScript JSON api. It can be used to implement JSON serialisation and deserialisation, and many of the community efforts rely on it.

What is the relationship between that one and this one Js.Json | ReScript API

Could you give a small tiny example on how I could use that binding?

Js.Json | ReScript API This doc would be updated soon with new modeling of Json in the compiler v11. https://github.com/rescript-lang/rescript-compiler/blob/master/jscomp/others/js_json.res

https://github.com/rescript-association/rescript-core/blob/main/src/Core__JSON.res This is core lib apis for Js.Json.t that is defined in the compiler above.

Back to your question about how to serialized and deserialize the Json data, I think there are two ways you can choose:

  1. Using std/core lib
    This is now idomatical way to handle Json data with v11: Better interop with customizable variants | ReScript Blog

  2. Using 3rd party lib
    There are some libraries to handle the Json data. I’m not going to link here, but you can refer to the benchmark: ReScript decoding libraries benchmark

EDIT: the benchmark page seems updated that is missing some libraries compat to ReScript.
Here are some libraries I know:

  1. rescript-struct
  2. ppx_spice
  3. bs-atdgen
3 Likes

Yeah, I mistook the fork and broken the page by syncing it with original master :sweat_smile:

1 Like

Thank you for clarifying!

1 Like

I wonder if this looks alright. I don’t like the way I am decoding the array below.

type t = {
  from: string,
  message: string,
  timestamp: float,
}

let encode = (message) => {
  [
    ("from", Js.Json.string(message.from)),
    ("message", Js.Json.string(message.message)),
    ("timestamp", Js.Json.number(message.timestamp)),
  ]
  -> Dict.fromArray
  -> Js.Json.object_
}

let serializeOne = (payload) => {
  payload
  -> encode
  -> Js.Json.stringify
}

let serializeMany = (payload) => {
  payload
  -> Array.map(encode)
  -> Js.Json.array
  -> Js.Json.stringify
}

let decode = (obj) => {
  switch Js.Json.decodeObject(obj) {
    | None => Error("Not an object")
    | Some(dict) => {
      let from = Dict.get(dict, "from")
      let message = Dict.get(dict, "message")
      let timestamp = Dict.get(dict, "timestamp")

      switch (from, message, timestamp) {
        | (Some(from), Some(message), Some(timestamp)) => {
          let from = Js.Json.decodeString(from)
          let message = Js.Json.decodeString(message)
          let timestamp = Js.Json.decodeNumber(timestamp)

          switch (from, message, timestamp) {
            | (Some(from), Some(message), Some(timestamp)) if from !== "" && message !== "" => {
              Ok({ from, message, timestamp })
            }
            | _ => {
              Error("Expected string from and string message")
            }
          }
        }
        | _ => Error("Expected non empty from and message")
      }
    }
  }
}

let deserializeOne = (payload) => {
  switch Message__Helper.parse(payload) {
    | Error(msg) => Error("Error parsing JSON: " ++ msg)
    | Ok(json) => decode(json)
  }
}

let deserializeMany = (payload) => {
  switch Message__Helper.parse(payload) {
    | Error(msg) => Error("Error parsing JSON: " ++ msg)
    | Ok(json) => {
      switch Js.Json.decodeArray(json) {
        | None => Error("Not an array")
        | Some(array) => {
          let result = ref(Ok([]))
          let idx = ref(0)

          while Result.isOk(result.contents) && idx.contents < Array.length(array) {
            let msg = array
              -> Array.get(idx.contents)
              -> Option.getExn
              -> decode

            switch msg {
              | Error(e) => {
                result.contents = Error(e)
              }
              | Ok(msg) => {
                result.contents
                -> Result.getExn
                -> Array.push(msg)
              }
            }

            idx.contents = idx.contents + 1
          }

          result.contents
        }
      }
    }
  }
}

With rescript-struct:

type t = {
  from: string,
  message: string,
  timestamp: float,
}

let struct = S.object(s => {
  from: s.field("from", S.string),
  message: s.field("message", S.string),
  timestamp: s.field("timestamp", S.float),
})

let value = data->S.parseWith(struct)

With ppx-spice:

@spice
type t = {
  from: string,
  message: string,
  timestamp: float,
}

let value = data->t_decode
4 Likes