#r "../packages/Newtonsoft.Json.12.0.3/lib/netstandard2.0/Newtonsoft.Json.dll"
type [<Struct; System.ComponentModel.TypeConverterAttribute(typeof<CC>)>] C = A of string
and CC() =
inherit System.ComponentModel.TypeConverter()
override _.CanConvertFrom (_, t) = t = typeof<string>
override _.ConvertFrom(_, _, s) = s :?> string |> A |> box<C>
override _.CanConvertTo (_, t) = t = typeof<string>
override _.ConvertTo(_, _, s, _) = s :?> C |> fun (A s) -> s |> box<string>
Newtonsoft.Json.JsonConvert.SerializeObject {|a = A "123"|}
This results in val it : string = "{"a":{"Case":"A","Fields":["123"]}}"
, which indicates that the TypeConverter
is not respected. This also happens for reference DUs.
However, this does not happen with JsonConverter
s:
#r "../packages/Newtonsoft.Json.12.0.3/lib/netstandard2.0/Newtonsoft.Json.dll"
type [<Struct; Newtonsoft.Json.JsonConverter(typeof<CC>)>] C = A of string
and CC() =
inherit Newtonsoft.Json.JsonConverter()
override _.CanConvert t = t = typeof<string>
override _.ReadJson (r, _, _, _) = r.ReadAsString() |> A |> box<C>
override _.WriteJson (w, v, _) = v :?> C |> fun (A s) -> s |> w.WriteValue
Newtonsoft.Json.JsonConvert.SerializeObject {|a = A "123"|}
This results in val it : string = "{"a":"123"}"
.
Compare this with records:
#r "../packages/Newtonsoft.Json.12.0.3/lib/netstandard2.0/Newtonsoft.Json.dll"
type [<Struct; System.ComponentModel.TypeConverterAttribute(typeof<CC>)>] C = { A: string }
and CC() =
inherit System.ComponentModel.TypeConverter()
override _.CanConvertFrom (_, t) = t = typeof<string>
override _.ConvertFrom(_, _, s) = { A = s :?> string } |> box<C>
override _.CanConvertTo (_, t) = t = typeof<string>
override _.ConvertTo(_, _, s, _) = (s :?> C).A |> box<string>
Newtonsoft.Json.JsonConvert.SerializeObject {|a = { A = "123"}|}
This also results in val it : string = "{"a":"123"}"
, which indicates that the TypeConverter
is respected.
This shows that something is preventing TypeConverter
s in discriminated unions from being recognized. What would be the reason? JsonConverter
s are not usable in dictionary keys, so I would expect TypeConverter
s to perform better. What would be a viable approach to correctly serialize the aforementioned discriminated union?
Your problem is that Json.NET has its own built-in converter for discriminated unions, DiscriminatedUnionConverter
. Any applicable JsonConverter
will always supersede an applied TypeConverter
.
A built-in converter can be disabled by providing your own, alternate, JsonConverter
, either in settings or via an applied JsonConverterAttribute
. You have already created a converter that correctly converts your type C
, but if you would prefer to fall back to the applied TypeConverter
, you can create a JsonConverter
that does nothing and falls back on default serialization by returning false
from CanRead
and CanWrite
:
type NoConverter<'a> () =
inherit JsonConverter()
override this.CanConvert(t) = (t = typedefof<'a>)
override this.CanRead = false
override this.CanWrite = false
override this.WriteJson(_, _, _) = raise (NotImplementedException());
override this.ReadJson(_, _, _, _) = raise (NotImplementedException());
Then apply it to your type as follows (demo fiddle #1 here):
type [<JsonConverterAttribute(typeof<NoConverter<C>>); System.ComponentModel.TypeConverterAttribute(typeof<CC>)>] C = A of string
and CC() =
inherit System.ComponentModel.TypeConverter()
override this.CanConvertFrom (_, t) = (t = typeof<string>)
override this.ConvertFrom(_, _, s) = s :?> string |> A |> box<C>
override this.CanConvertTo (_, t) = t = typeof<string>
override this.ConvertTo(_, _, s, _) = s :?> C |> fun (A s) -> s |> box<string>
printfn "%s" (Newtonsoft.Json.JsonConvert.SerializeObject(A "123"))
Or, use it in settings as follows (demo fiddle #2 here):
let settings = JsonSerializerSettings(Converters = [|NoConverter<C>()|])
printfn "%s" (Newtonsoft.Json.JsonConvert.SerializeObject(A "123", settings))