Json in Standard ML

Here’s the translation of the JSON example from Go to Standard ML, along with explanations in Markdown format suitable for Hugo:

(* Standard ML offers libraries for JSON encoding and decoding, 
   including to and from built-in and custom data types. 
   We'll use the JSON structure from the SML/NJ Library. *)

structure JSON = JSONLib

(* We'll use these two records to demonstrate encoding and
   decoding of custom types below. *)

type response1 = {
    page: int,
    fruits: string list
}

(* In Standard ML, we don't have struct tags like in Go.
   Instead, we can define custom encoding/decoding functions. *)

type response2 = {
    page: int,
    fruits: string list
}

(* Let's look at encoding basic data types to JSON strings. 
   Here are some examples for atomic values. *)

val boolJson = JSON.bool true
val _ = print(JSON.fmt boolJson ^ "\n")

val intJson = JSON.int 1
val _ = print(JSON.fmt intJson ^ "\n")

val floatJson = JSON.real 2.34
val _ = print(JSON.fmt floatJson ^ "\n")

val strJson = JSON.string "gopher"
val _ = print(JSON.fmt strJson ^ "\n")

(* And here are some for lists and dictionaries, which encode
   to JSON arrays and objects as you'd expect. *)

val slcJson = JSON.array (map JSON.string ["apple", "peach", "pear"])
val _ = print(JSON.fmt slcJson ^ "\n")

val mapJson = JSON.object [
    ("apple", JSON.int 5),
    ("lettuce", JSON.int 7)
]
val _ = print(JSON.fmt mapJson ^ "\n")

(* The JSON library can automatically encode your
   custom data types if you provide a conversion function. *)

fun response1ToJson (r : response1) : JSON.json =
    JSON.object [
        ("Page", JSON.int (#page r)),
        ("Fruits", JSON.array (map JSON.string (#fruits r)))
    ]

val res1 : response1 = {
    page = 1,
    fruits = ["apple", "peach", "pear"]
}
val res1Json = response1ToJson res1
val _ = print(JSON.fmt res1Json ^ "\n")

(* Now let's look at decoding JSON data into SML values. 
   Here's an example for a generic data structure. *)

val jsonStr = "{\"num\":6.13,\"strs\":[\"a\",\"b\"]}"
val jsonData = valOf (JSON.parse jsonStr)

(* To access values, we need to pattern match on the JSON structure *)

fun getNum json =
    case json of
        JSON.OBJECT fields =>
            (case List.find (fn (k, _) => k = "num") fields of
                 SOME (_, JSON.REAL r) => r
               | _ => raise Fail "num field not found or not a number")
      | _ => raise Fail "not an object"

fun getStrs json =
    case json of
        JSON.OBJECT fields =>
            (case List.find (fn (k, _) => k = "strs") fields of
                 SOME (_, JSON.ARRAY strs) => 
                     List.map (fn (JSON.STRING s) => s | _ => raise Fail "not a string") strs
               | _ => raise Fail "strs field not found or not an array")
      | _ => raise Fail "not an object"

val num = getNum jsonData
val _ = print(Real.toString num ^ "\n")

val strs = getStrs jsonData
val _ = print(List.hd strs ^ "\n")

(* We can also decode JSON into custom data types. 
   This requires writing a custom parsing function. *)

fun parseResponse2 json =
    case json of
        JSON.OBJECT fields =>
            let
                val page = case List.find (fn (k, _) => k = "page") fields of
                               SOME (_, JSON.INT i) => i
                             | _ => raise Fail "page field not found or not an integer"
                val fruits = case List.find (fn (k, _) => k = "fruits") fields of
                                 SOME (_, JSON.ARRAY fs) => 
                                     List.map (fn (JSON.STRING s) => s | _ => raise Fail "not a string") fs
                               | _ => raise Fail "fruits field not found or not an array"
            in
                {page = page, fruits = fruits} : response2
            end
      | _ => raise Fail "not an object"

val res2Str = "{\"page\": 1, \"fruits\": [\"apple\", \"peach\"]}"
val res2 = parseResponse2 (valOf (JSON.parse res2Str))
val _ = print(Int.toString (#page res2) ^ "\n")
val _ = print(List.hd (#fruits res2) ^ "\n")

(* In Standard ML, we typically work with strings for JSON input/output,
   rather than directly with file handles or streams. However, you can
   easily combine this with file I/O functions if needed. *)

This Standard ML code demonstrates JSON encoding and decoding, including custom data types. Note that Standard ML doesn’t have built-in JSON support, so we’re using a hypothetical JSONLib structure. In practice, you’d need to use or implement a JSON library for Standard ML.

The concepts are similar to the Go version, but the syntax and some implementation details are different due to Standard ML’s functional nature and type system. For example, we use pattern matching extensively for JSON parsing, and we define custom functions for encoding and decoding our custom types.