F# "Код недостаточно универсален" "^T не может быть обобщен" при реализации интерфейса

Я пытаюсь реализовать IJsonSerializer от Giraffe.Serialization.Json в проекте для использования Microsoft.FSharpLu.Json, но у меня возникли проблемы с одним из общих методов (код ниже)

type FSharpLuSerializer () =
    interface Giraffe.Serialization.Json.IJsonSerializer with
        member __.Deserialize<'T> (json : string) =
            Microsoft.FSharpLu.Json.Default.deserialize<'T> json

Мне дают ошибку

Этот код не является достаточно общим. Переменная типа ^T не может быть обобщена, поскольку она выходит за пределы своей области видимости.

Я видел другие вопросы с той же ошибкой, но я не уверен, как применить их решения в моей ситуации. Я предполагаю, что это как-то связано с FSharpLu.Json, использующим ^T, но я не знаю, каким будет мой обходной путь

https://github.com/Microsoft/fsharplu/blob/master/FSharpLu.Json/Default.fs

Вот интерфейс Giraffe IJSonSerializer

[<AllowNullLiteral>]
type IJsonSerializer =
    abstract member SerializeToString<'T>      : 'T -> string
    abstract member SerializeToBytes<'T>       : 'T -> byte array
    abstract member SerializeToStreamAsync<'T> : 'T -> Stream -> Task

    abstract member Deserialize<'T>      : string -> 'T
    abstract member Deserialize<'T>      : byte[] -> 'T
    abstract member DeserializeAsync<'T> : Stream -> Task<'T>

3 ответа

Решение

Я столкнулся с этим, когда столкнулся с той же проблемой. Я только что написал это, и это, кажется, работает. Я прогоню его через несколько шагов, и если это будет хорошо, я выложу пакет NuGet, где его можно подключить.

open Giraffe.Serialization
open Microsoft.FSharpLu.Json
open Newtonsoft.Json
open System.IO
open System.Text
open System.Threading.Tasks

type FSharpLuJsonSerializer() =
  interface IJsonSerializer with
    member __.SerializeToString<'T>      (x : 'T) =
      Compact.serialize x
    member __.SerializeToBytes<'T>       (x : 'T) =
      Text.Encoding.UTF8.GetBytes (Compact.serialize x)
    member __.SerializeToStreamAsync<'T> (x : 'T) (stream : Stream) =
      let serializer = new JsonSerializer()
      serializer.Converters.Add (CompactUnionJsonConverter ())
      use sw = new StreamWriter (stream)
      use writer = new JsonTextWriter (sw)
      serializer.Serialize (writer, obj)
      Task.CompletedTask

    member __.Deserialize<'T> (json : string) =
      (Compact.deserialize >> box) json :?> 'T
    member __.Deserialize<'T> (bytes : byte[]) =
      (Compact.deserialize >> box) (Encoding.UTF8.GetString (ReadOnlySpan bytes)) :?> 'T
    member __.DeserializeAsync<'T> (stream : Stream) =
      Task.FromResult<'T> ((Compact.deserializeStream >> box) stream :?> 'T)

Отзывы от F# также приветствуются.

После прыжка на F# Slack, есть еще более простой способ сделать это, не требуя всего бокса и кастинга. FSharpLu использует Newtonsoft.Json под капотом, и его компактная функциональность исходит от CompactUnionJsonConverter тип. Вы можете добавить этот конвертер к настройкам Giraffe по умолчанию JsonSerializerSettingsи это работает - это означает, что вам не нужно полностью переопределять IJsonSerializer,

  open Giraffe.Serialization
  open Microsoft.FSharpLu.Json
  open Newtonsoft.Json

  let jsonSettings = 
    let x = NewtonsoftJsonSerializer.DefaultSettings
    x.Converters.Add (CompactUnionJsonConverter (true))
    x

Затем, где вы настраиваете свои услуги...

    services.AddSingleton<IJsonSerializer>(NewtonsoftJsonSerializer jsonSettings)

Огромное спасибо Нино Флорису (@ninofloris) за это предложение.

Это беспорядочно, в общем, интерфейсы должны захватывать тип, который будет применен через обобщенные методы, чтобы еще больше усложнить ситуацию, ваша библиотека FSharpLu.Json использует встроенные SRTP, встроенный шаблон SRTP вызов метода для типа ограничения ^ поэтому в скомпилированном коде есть конкретный метод фиксированного типа для каждого вызова, он отличается от обычных обобщенных обобщенных типов, используемых в.net, которые разрешают тип во время выполнения.

Поскольку вы не можете встроить интерфейс, попробуйте добавить ограничение типа для интерфейса, которое будет перехватывать тип и избегать проблемы обобщения.

type IJsonSerializer<'T> =
abstract member SerializeToString      : 'T -> string
...
Другие вопросы по тегам