2
#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 JsonConverters:

#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 TypeConverters in discriminated unions from being recognized. What would be the reason? JsonConverters are not usable in dictionary keys, so I would expect TypeConverters to perform better. What would be a viable approach to correctly serialize the aforementioned discriminated union?

dbc
  • 104,963
  • 20
  • 228
  • 340
Happypig375
  • 1,022
  • 1
  • 12
  • 32
  • I think I know what your problem is, but I can't get your code to compile. See https://dotnetfiddle.net/hWmNcz. Can you please [edit] your question to fix compilation? Does the following correctly fix the compilation? https://dotnetfiddle.net/lQ4Ouf. A [mcve] would be awesome. – dbc Dec 11 '19 at 16:39

1 Answers1

2

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))
dbc
  • 104,963
  • 20
  • 228
  • 340