Почему F# Discriminated Union не может использовать свой TypeConverter в JSON.NET, а другие типы могут?

#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"|}

Это приводит к val it : string = "{"a":{"Case":"A","Fields":["123"]}}", что указывает на то, что TypeConverter не соблюдается. Это также происходит для эталонных DU.

Однако с 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"|}

Это приводит к val it : string = "{"a":"123"}".

Сравните это с записями:

#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"}|}

Это также приводит к val it : string = "{"a":"123"}", что указывает на то, что TypeConverter соблюдается.

Это показывает, что что-то мешает распознаванию TypeConverters в размеченных объединениях. В чем причина? JsonConverters нельзя использовать в словарных ключах, поэтому я ожидаю, что TypeConverters будут работать лучше. Какой был бы жизнеспособный подход для правильной сериализации вышеупомянутого размеченного союза?


person Happypig375    schedule 11.12.2019    source источник
comment
Я думаю, что знаю, в чем ваша проблема, но я не могу скомпилировать ваш код. См. dotnetfiddle.net/hWmNcz. Не могли бы вы отредактировать свой вопрос, чтобы исправить компиляцию? Правильно ли исправляет компиляцию следующее? dotnetfiddle.net/lQ4Ouf. минимально воспроизводимый пример был бы потрясающим.   -  person dbc    schedule 11.12.2019


Ответы (1)


Ваша проблема в том, что Json.NET имеет собственный встроенный преобразователь для размеченных союзов, DiscriminatedUnionConverter. Любой применимый JsonConverter всегда заменит примененный TypeConverter.

Встроенный преобразователь можно отключить, указав свой собственный альтернативный JsonConverter либо в настройки или через примененный JsonConverterAttribute. Вы уже создали преобразователь, который правильно преобразует ваш тип C, но если вы предпочитаете вернуться к применяемому TypeConverter, вы можете создать JsonConverter, который ничего не делает и возвращается к сериализации по умолчанию, возвращая false из CanRead и 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());

Затем примените его к своему типу следующим образом (демонстрационная скрипта №1 здесь):

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"))

Или используйте его в настройках следующим образом (демонстрационная скрипта №2 здесь):

let settings = JsonSerializerSettings(Converters = [|NoConverter<C>()|])
printfn "%s" (Newtonsoft.Json.JsonConvert.SerializeObject(A "123", settings))
person dbc    schedule 11.12.2019