Json in F#

open System
open System.Text.Json
open System.Text.Json.Serialization

// We'll use these two record types to demonstrate encoding and
// decoding of custom types below.
type Response1 = {
    Page: int
    Fruits: string[]
}

// Only fields with public setters will be encoded/decoded in JSON.
// We can use attributes to customize the encoded JSON key names.
type Response2 = {
    [<JsonPropertyName("page")>]
    Page: int
    [<JsonPropertyName("fruits")>]
    Fruits: string[]
}

let main() =
    // First we'll look at encoding basic data types to
    // JSON strings. Here are some examples for atomic values.
    let bolB = JsonSerializer.Serialize(true)
    printfn "%s" bolB

    let intB = JsonSerializer.Serialize(1)
    printfn "%s" intB

    let fltB = JsonSerializer.Serialize(2.34)
    printfn "%s" fltB

    let strB = JsonSerializer.Serialize("gopher")
    printfn "%s" strB

    // And here are some for arrays and maps, which encode
    // to JSON arrays and objects as you'd expect.
    let slcD = [|"apple"; "peach"; "pear"|]
    let slcB = JsonSerializer.Serialize(slcD)
    printfn "%s" slcB

    let mapD = Map.ofList [("apple", 5); ("lettuce", 7)]
    let mapB = JsonSerializer.Serialize(mapD)
    printfn "%s" mapB

    // The JSON package can automatically encode your
    // custom data types.
    let res1D = { Page = 1; Fruits = [|"apple"; "peach"; "pear"|] }
    let res1B = JsonSerializer.Serialize(res1D)
    printfn "%s" res1B

    // You can use attributes on record field declarations
    // to customize the encoded JSON key names. Check the
    // definition of `Response2` above to see an example
    // of such attributes.
    let res2D = { Page = 1; Fruits = [|"apple"; "peach"; "pear"|] }
    let res2B = JsonSerializer.Serialize(res2D)
    printfn "%s" res2B

    // Now let's look at decoding JSON data into F#
    // values. Here's an example for a generic data
    // structure.
    let byt = """{"num":6.13,"strs":["a","b"]}"""

    // We need to provide a type where the JSON
    // package can put the decoded data. This
    // Map<string, JsonElement> will hold a map of strings
    // to arbitrary data types.
    let dat = JsonSerializer.Deserialize<Map<string, JsonElement>>(byt)
    printfn "%A" dat

    // In order to use the values in the decoded map,
    // we'll need to convert them to their appropriate type.
    // For example here we convert the value in `num` to
    // the expected `float` type.
    let num = (dat.["num"].GetDouble())
    printfn "%f" num

    // Accessing nested data requires a series of
    // conversions.
    let strs = dat.["strs"].EnumerateArray() |> Seq.map (fun x -> x.GetString()) |> Seq.toArray
    let str1 = strs.[0]
    printfn "%s" str1

    // We can also decode JSON into custom data types.
    // This has the advantages of adding additional
    // type-safety to our programs and eliminating the
    // need for type assertions when accessing the decoded
    // data.
    let str = """{"page": 1, "fruits": ["apple", "peach"]}"""
    let res = JsonSerializer.Deserialize<Response2>(str)
    printfn "%A" res
    printfn "%s" res.Fruits.[0]

    // In the examples above we always used strings as
    // intermediates between the data and JSON representation.
    // We can also stream JSON encodings directly to TextWriters
    // like Console.Out or even HTTP response streams.
    let d = Map.ofList [("apple", 5); ("lettuce", 7)]
    JsonSerializer.Serialize(Console.Out, d)

main()

This F# code demonstrates JSON encoding and decoding using the System.Text.Json namespace, which is the modern JSON handling library in .NET. Here are some key points about the translation:

  1. F# uses record types instead of struct types for defining custom data structures.

  2. The json tags in Go are replaced with JsonPropertyName attributes in F#.

  3. F# uses Map<'Key, 'Value> instead of Go’s map[KeyType]ValueType.

  4. The interface{} type in Go is roughly equivalent to JsonElement in F#’s JSON handling.

  5. F# uses pattern matching and functional programming constructs, which can make some operations more concise.

  6. Error handling in F# is typically done through the use of Option types or exceptions, rather than explicit error returns as in Go.

  7. F# doesn’t have a direct equivalent to Go’s panic, but you can use exceptions for similar behavior.

  8. The JsonSerializer class in System.Text.Json provides methods for both serialization and deserialization.

This code provides a comprehensive overview of JSON handling in F#, covering encoding of basic types, custom types, decoding into generic and specific types, and working with nested JSON structures.