1

I have an F# Record type

type MyType = {
  Name : string
  Description : string option
}

I'd like to get two arrays, one containing the names of the required properties and one containing the optional properties. How can I do this?

Brett Rowberry
  • 1,030
  • 8
  • 21

2 Answers2

1
open System.Reflection

/// inspired by https://stackoverflow.com/questions/20696262/reflection-to-find-out-if-property-is-of-option-type
let isOption (p : PropertyInfo) =
    p.PropertyType.IsGenericType &&
    p.PropertyType.GetGenericTypeDefinition() = typedefof<Option<_>>

/// required and optional property names of a type 'T - in that order
/// inspired by https://stackoverflow.com/questions/14221233/in-f-how-to-pass-a-type-name-as-a-function-parameter
/// inspired by https://stackoverflow.com/questions/59421595/is-there-a-way-to-get-record-fields-by-string-in-f
let requiredAndOptionalPropertiesOf<'T> =
    let optionals, requireds = typeof<'T>.GetProperties() |> Array.partition isOption
    let getNames (properties : PropertyInfo[]) = properties |> Array.map (fun f -> f.Name)
    (getNames requireds, getNames optionals)
Brett Rowberry
  • 1,030
  • 8
  • 21
  • 1
    I'd prefer to use `FSharpType.GetRecordFields typeof` so it won't break anything if the spec changes. – Asti Jul 18 '20 at 11:04
  • I added an alternative answer 'reflecting' your comment (pun intended). How is `FSharp.Reflection.FSharpType.GetRecordFields` safer than `typeof<'T>.GetProperties()`? – Brett Rowberry Jul 20 '20 at 13:42
  • 1
    `GetRecordFields` is part of the API surface, and even if the internal implementation of how properties map to record fields change, the API behavior will stay the same. That being said, `GetRecordFields` will throw if T is not a record type, and only returns properties with a field mapping (i.e, `CompilationMappingAttribute` applied), and respects declared ordering. – Asti Jul 20 '20 at 15:20
0

Here is an alternative answer that takes into account the comment by @Asti:

open System.Reflection

let isOption (p : PropertyInfo) =
    p.PropertyType.IsGenericType &&
    p.PropertyType.GetGenericTypeDefinition() = typedefof<Option<_>>

let requiredAndOptionalPropertiesOf<'T> =
    let optionals, requireds = FSharp.Reflection.FSharpType.GetRecordFields typeof<'T> |> Array.partition isOption
    let getNames (properties : PropertyInfo[]) = properties |> Array.map (fun f -> f.Name)
    (getNames requireds, getNames optionals)
Brett Rowberry
  • 1,030
  • 8
  • 21