I have some data coming from some system via a TcpClient.
The system is a kind of column store with it's own way of representing null values. The system supports null for any type including int, DateTime etc.
The TcpClient returns values like DateTime.MinValue
in case of nulls. But the client also has a null checking utility.
I have put together some reflection code to generate types, which works fine as long as I don't have any option types. I want to adapt this code to map null values to Option<'t>
.
So far, I have the following utility.
let checkNull (x:obj) =
let elementType = x.GetType()
let defType = typedefof<ValueOption<_>>;
let genericType = defType.MakeGenericType elementType
let optionCaseInfoSome, _ = FSharpValue.GetUnionFields(DateTime.Now |> ValueSome :> obj, genericType)
let valueNone:ValueOption<DateTime> = ValueNone
let optionCaseInfoNone, _ = FSharpValue.GetUnionFields(valueNone :> obj, genericType)
if KdbConstants.IsNull x then
FSharpValue.MakeUnion(optionCaseInfoNone, Array.empty)
else
FSharpValue.MakeUnion(optionCaseInfoSome, [|x|])
This will only work for Option<DateTime>
values. How can I adapt this to work for any Option<
t>`?
PS: I am aware that my utility is not efficient and much of the values can be pre-calculated, rather than being run each time the function is called. I will clean it up once I get it to work.
CodePudding user response:
I think I have an answer to this
let makeOptionCaseMetaData (x:obj) =
let elementType = x.GetType()
let defType = typedefof<Option<_>>;
let genericType = defType.MakeGenericType elementType
let optionCaseInfoNone, optionCaseInfoSome = FSharpType.GetUnionCases(genericType) |> (fun a -> a[0], a[1])
{OptionCaseInfoSome = optionCaseInfoSome; OptionCaseInfoNone = optionCaseInfoNone}
let checkNull (x:obj) =
let metaData = makeOptionCaseMetaData x
if KdbConstants.IsNull x then
FSharpValue.MakeUnion(metaData.OptionCaseInfoNone, Array.empty)
else
FSharpValue.MakeUnion(metaData.OptionCaseInfoSome, [|x|])
Still not very efficient but I can worry about that separately.
CodePudding user response:
I think you can simplify this and avoid using some of the reflection. You can define a normal generic method to do the conversion:
// For illustration, turn "" to `null`
let isNull (x:obj) = unbox x = ""
// Static method (easier to access via reflection) that does the null check
type OptionConverter =
static member CheckNull<'T>(x:'T) =
if isNull x then None
else Some x
If you now get some object, you can create a generic instantiation of CheckNull
and invoke the method to do the work:
let checkNull (x:obj) =
let checkMi = typeof<OptionConverter>.GetMethod("CheckNull")
checkMi.MakeGenericMethod(x.GetType()).Invoke(null, [| x |])
checkNull "" // None (shows as `<null>` in FSI because it is of type `obj`)
checkNull "Yo" // Some "Yo"