Skip to content

Instantly share code, notes, and snippets.

@mbuhot
Last active November 5, 2023 20:16
Show Gist options
  • Save mbuhot/c224f15e0266adf5ba8ca4e882f88a75 to your computer and use it in GitHub Desktop.
Save mbuhot/c224f15e0266adf5ba8ca4e882f88a75 to your computer and use it in GitHub Desktop.
System.Text.Json converters for F# Option, List and Map types
namespace System.Text.Json
open System
open System.Collections.Generic
open System.Text.Json.Serialization
// Converts Option<T> to/from JSON by projecting to null or T
type OptionValueConverter<'T>() =
inherit JsonConverter<'T option>()
override this.Read (reader: byref<Utf8JsonReader>, _typ: Type, options: JsonSerializerOptions) =
match reader.TokenType with
| JsonTokenType.Null -> None
| _ -> Some <| JsonSerializer.Deserialize<'T>(&reader, options)
override this.Write (writer: Utf8JsonWriter, value: 'T option, options: JsonSerializerOptions) =
match value with
| None -> writer.WriteNullValue ()
| Some value -> JsonSerializer.Serialize(writer, value, options)
// Instantiates the correct OptionValueConverter<T>
type OptionConverter() =
inherit JsonConverterFactory()
override this.CanConvert(t: Type) : bool =
t.IsGenericType &&
t.GetGenericTypeDefinition() = typedefof<Option<_>>
override this.CreateConverter(typeToConvert: Type,
_options: JsonSerializerOptions) : JsonConverter =
let typ = typeToConvert.GetGenericArguments() |> Array.head
let converterType = typedefof<OptionValueConverter<_>>.MakeGenericType(typ)
Activator.CreateInstance(converterType) :?> JsonConverter
// Converts Map<K,V> to/from JSON by projecting to Dictionary<K,V>
type MapValueConverter<'K, 'V when 'K : comparison>() =
inherit JsonConverter<Map<'K, 'V>>()
override this.Read (reader: byref<Utf8JsonReader>, _typ: Type, options: JsonSerializerOptions) =
JsonSerializer.Deserialize<System.Collections.Generic.Dictionary<'K, 'V>>(&reader, options)
|> Seq.map (|KeyValue|)
|> Map.ofSeq
override this.Write (writer: Utf8JsonWriter, value: Map<'K, 'V>, options: JsonSerializerOptions) =
let dictionary = Dictionary<'K, 'V>()
value |> Map.iter (fun k v -> dictionary.Add(k, v))
JsonSerializer.Serialize(writer, dictionary, options)
// Instantiates the correct MapValueConverter<T>
type MapConverter() =
inherit JsonConverterFactory()
override this.CanConvert(t: Type) : bool =
t.IsGenericType &&
List.contains (t.GetGenericTypeDefinition()) [typedefof<Map<_, _>>; typedefof<IDictionary<_,_>>]
override this.CreateConverter(typeToConvert: Type,
_options: JsonSerializerOptions) : JsonConverter =
let typArgs = typeToConvert.GetGenericArguments()
let converterType = typedefof<MapValueConverter<_,_>>.MakeGenericType(typArgs)
Activator.CreateInstance(converterType) :?> JsonConverter
// Converts List<T> to/from JSON by projecting to IEnumerable<T>
type ListValueConverter<'T>() =
inherit JsonConverter<'T list>()
override this.Read (reader: byref<Utf8JsonReader>, _typ: Type, options: JsonSerializerOptions) =
JsonSerializer.Deserialize<'T seq>(&reader, options)
|> List.ofSeq
override this.Write (writer: Utf8JsonWriter, value: 'T list, options: JsonSerializerOptions) =
JsonSerializer.Serialize(writer, (List.toSeq value), options)
// Instantiates the correct ListValueConverter<T>
type ListConverter() =
inherit JsonConverterFactory()
override this.CanConvert(t: Type) : bool =
t.IsGenericType &&
List.contains (t.GetGenericTypeDefinition()) [typedefof<list<_>>; typedefof<IReadOnlyCollection<_>>]
override this.CreateConverter(typeToConvert: Type,
_options: JsonSerializerOptions) : JsonConverter =
let typArgs = typeToConvert.GetGenericArguments()
let converterType = typedefof<ListValueConverter<_>>.MakeGenericType(typArgs)
Activator.CreateInstance(converterType) :?> JsonConverter
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment