Arshia001 / FSharp.GrpcCodeGenerator

A protoc plugin to enable generation of F# code + supporting libraries
MIT License
81 stars 9 forks source link

Trying to use the message formatter throws "Unable to format value of type Microsoft.FSharp.Core.FSharpValueOption`1" for optional types #19

Open marner2 opened 2 years ago

marner2 commented 2 years ago

This happens when trying to use the JsonFormatter for types with any optional fields.

Simple proto file:

message TestMessage {
  optional string TestField = 1;
}

Simple test:

let x = { TestMessage.empty() with TestField = ValueSome "hello" }
let string = Google.Protobuf.JsonFormatter.Default.Format(x)

Result:

  Message: 
System.ArgumentException : Unable to format value of type Microsoft.FSharp.Core.FSharpValueOption`1[System.String]

  Stack Trace: 
JsonFormatter.WriteValue(TextWriter writer, Object value)
JsonFormatter.WriteMessageFields(TextWriter writer, IMessage message, Boolean assumeFirstFieldWritten)
JsonFormatter.WriteMessage(TextWriter writer, IMessage message)
JsonFormatter.Format(IMessage message, TextWriter writer)
JsonFormatter.Format(IMessage message)
marner2 commented 2 years ago

You probably need something like this integrated into an FSharp version of the JsonFormatter (but for valueoptions):

type OptionConverter() =
  inherit JsonConverter()

  override x.CanConvert(t) = t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<option<_>>

  override x.WriteJson(writer, outer, serializer) =

    let inner =
      if outer = null then
        null
      else
        let _, fields = FSharpValue.GetUnionFields(outer, outer.GetType())
        fields[0]

    if outer <> null && inner = null then
      writer.WriteStartObject()
      writer.WritePropertyName("Case")
      writer.WriteValue("None")
      writer.WriteEndObject()
    else
      serializer.Converters.Remove(x) |> ignore
      serializer.Serialize(writer, inner)
      serializer.Converters.Add(x)

  override x.ReadJson(reader, t, _existingValue, serializer) =
    let innerType = t.GetGenericArguments().[0]

    let innerType = if innerType.IsValueType then typedefof<Nullable<_>>.MakeGenericType [| innerType |] else innerType

    serializer.Converters.Remove(x) |> ignore
    let value = serializer.Deserialize(reader, innerType)
    serializer.Converters.Add(x)

    let cases = FSharpType.GetUnionCases(t)
    FSharpValue.MakeUnion(cases[1], [| value |])