How do I check 'T for types not being allowed to be null?

Viewed 110

given

let inline deserialize<'t> x :'t option = 
    printfn "Attempting to deserialize %A" typeof<'t>.Name
    try
        JsonConvert.DeserializeObject<'t>(x)
        |> Some
    with ex ->
        System.Diagnostics.Trace.WriteLine(sprintf "Error deserialization failed:%s" ex.Message)
        None

is returning for example an obj list as null. FSharpList<_> is not allowed to be null. How can I, without knowing what 't is ask F# if the type I'm about to return supports null so that I can halt/throw/act accordingly? Is there a reflection flag or Microsoft.FSharp.Reflection... method for this?

2 Answers

The full answer involves checking if the type is a record (in which case null is never allowed), or if it's a union (in which case null is allowed if the type has a CompilationRepresentation CustomAttribute whose flags contain the UseNullAsTrueValue member (https://msdn.microsoft.com/en-us/visualfsharpdocs/conceptual/core.compilationrepresentationflags-enumeration-%5Bfsharp%5D for more details)).

To answer the first question you can use the IsRecord function in the FSharpType module (https://msdn.microsoft.com/en-us/visualfsharpdocs/conceptual/reflection.fsharptype-class-%5Bfsharp%5D) and to answer the second you can use a combination of the IsUnion function on that same module and CustomAttribute hunting.

In case the type is a union with UseNullAsTrueValue set, you should be good to go, just send the value along.

Best I can think of is to box the result (in case you are deserializing structs) and pattern match it with null:

  let inline deserialize<'t> x :'t option = 
      printfn "Attempting to deserialize %A" typeof<'t>.Name
      try
          let obj = Newtonsoft.Json.JsonConvert.DeserializeObject<'t>(x)
          match box obj with
          | null -> None
          | _ -> Some obj
      with ex ->
          System.Diagnostics.Trace.WriteLine(sprintf "Error deserialization failed:%s" ex.Message)
          None

  let r1 = deserialize<obj list> ("[1,2,3]") //val r1 : obj list option = Some [1L; 2L; 3L]
  let r2 = deserialize<obj list> ("null") //val r2 : obj list option = None
Related