Serializing F# Option types - f#

Consider the F# fragment below:
type MyType = {
CrucialProperty: int
OptionalProperty: string option
}
let first = { CrucialProperty = 500; OptionalProperty = Some("Hello")}
let second = { CrucialProperty = 500; OptionalProperty = Some(null)}
let third = { CrucialProperty = 500; OptionalProperty = None}
I wish to do serialize this type using JSON.NET so I get the following strings respectively for the cases described above:
{"CrucialProperty":500,"OptionalProperty":"Hello"}
{"CrucialProperty":500,"OptionalProperty":null}
{"CrucialProperty":500}
Essentially, the problem boils down to being able to include/exclude a property in the serialized output based on the value of that property.
I've managed to find a few "OptionConverters" out there (e.g here), but they don't quite seem to do what I'm looking for.

I would recommend FifteenBelow's converters which work with JSON.NET but provide serialization F# types https://github.com/15below/FifteenBelow.Json (apparently moved to https://github.com/kolektiv/FifteenBelow.Json).
From their usage section:
let converters =
[ OptionConverter () :> JsonConverter
TupleConverter () :> JsonConverter
ListConverter () :> JsonConverter
MapConverter () :> JsonConverter
BoxedMapConverter () :> JsonConverter
UnionConverter () :> JsonConverter ] |> List.toArray :> IList<JsonConverter>
let settings =
JsonSerializerSettings (
ContractResolver = CamelCasePropertyNamesContractResolver (),
Converters = converters,
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore)
Specifically what you're looking for is the the NullValueHandling = NullValueHandling.Ignore bit.

The FSharp.JsonSkippable library allows you to control in a simple and strongly typed manner whether to include a given property when serializing (and determine whether a property was included when deserializing), and moreover, to control/determine exclusion separately of nullability. (Full disclosure: I'm the author of the library.)

Related

Confused about static dictionary in a type, in F#

With this type:
type A =
{
S: string
}
static member private l = Dictionary<string, A>()
static member add s = A.l.[s] <- { S=s }
static member list () = l.Values
if I do:
A.add "hello"
A.add "world"
I'd expect A.list() to return something since the dictionary is static, but it returns an empty list. Why is that?
To clarify what I'm trying to do: I'd like to have the ability to register the objects of type A into a static dictionary that is attached to the type itself as it would make the object repository 'self contained' in the type, in a way.
Your l is not a field, but a property with a getter.
A "property", contrary to appearances, is not a memory cell with some value in it. A "property" is a pair of get+set functions. Just functions, that's all. No memory cell.
So what you made yourself is a property with a getter (without a setter), and all that getter does is create a new Dictionary and return it.
This means, every time you access A.l, you get yourself a new, fresh dictionary. Because l is a function, not a memory cell.
Now, in order to make a memory cell (aka "field"), one would ordinarily use static member val, like so:
static member val private l = Dictionary<string, A>()
Unfortunately, in this particular case this doesn't work, because static fields are not permitted on F# records and unions. They work fine on actual classes, but not on F# types.
So instead what I would recommend is to put those functions in a module rather than making them static methods:
type A = { S: string }
module A =
let private l = Dictionary<string, A>()
let add s = l.[s] <- { S=s }
let list () = l.Values
(and just in general: try to use fewer classes and more modules and functions; they're more idiomatic in F# and lead to fewer problems in general)
Now this works as expected:
> A.add "hello";;
val it : unit = ()
> A.add "world";;
val it : unit = ()
> A.list();;
val it : Dictionary`2.ValueCollection<string,A> =
seq [{ S = "hello" }; { S = "world" }]

Compiler unable to resolve type from shared 'header' file

In Common.fs:
namespace Bug
module Common =
type SourceEntity = {
id : int
link : string
}
type ReleaseEntity = {
id : int
notes : string
}
In Release.fs
namespace Bug
open System
open Common
module Release =
let cache = new Collections.Generic.Dictionary<int, ReleaseEntity>()
let AddToCache(entity) =
cache.Add(entity.id, entity)
()
let AddRec() =
let entity : ReleaseEntity = {
id = 1
notes = "Notes"
}
AddToCache(entity)
In Source.fs
namespace Bug
open System
open Common
module Source =
let Cache = new Collections.Generic.Dictionary<int, SourceEntity>()
let AddToCache(entity) =
Cache.Add(entity.id, entity) <<=== E R R O R
()
let AddRec() =
let ent : SourceEntity = {
id = 1
releases = "Releases"
}
AddToCache(ent) <<=== E R R O R
Files included in above order in the Visual Studio project.
Error reported in Source.fs:
Error FS0001 This expression was expected to have type
'SourceEntity'
but here has type
'ReleaseEntity'
If the order of the two types in Common.fs are reversed, the error is reported in Release.fs where expected type is ReleaseEntity but has type SourceEntity.
Any ideas why this error is happening?
It's a clash (and shadowing) of record field names.
When you write entity.id in the body of Bug.Source.AddToCache, the compiler uses the fact that you're accessing the .id field to infer the type of entity. Which records have a field named id? Well, those two records do, but the compiler has to pick one. How? Easy: the last one takes precedence. This is called "shadowing".
In order to disambiguate the choice, just add a type annotation:
let AddToCache(entity) =
Cache.Add(entity.id, entity)
()
Wait, but why doesn't the compiler use the type of Cache.Add to infer the type of entity?
Well, this is just a limitation (or a feature?) of F#. The compilation is single-pass, type interference proceeds top down, left to right, without doublebacks. This allows the compiler to be very fast and very predictable (looking at you, Haskell).
But in this case it means that by the time the compiler sees that entity is used as parameter in Cache.Add, it has already decided what its type must be.
When you get a type error, try to think about how the compiler came to infer that particular type.
Here:
let AddToCache(entity) =
cache.Add(entity.id, entity)
()
Could the compiler know which type has would have an id field in entity?
If you had typed in
let entity = { id = 1; link = "" }
the compiler would infer that this is SourceEntity because only SourceEntity has those particular record fields. In cache.Add(entity.id, entity), the compiler has no other constraints to go by, other than it has to have an id field, so it picks the last matching type - and that is why you get the error.
If you refactor the common id field to
namespace Bug
module Common =
type SourceEntity = {
source_id : int
link : string
}
type ReleaseEntity = {
release_id : int
notes : string
}
you will find that the error disappears.
Solutions
All of the solutions involve constraining it to a known type.
The simplest is to add a type annotation:
let AddToCache(entity: SourceEntity) =
Another is to deconstruct it explicitly:
let { SourceEntity.id = id } = entity
Cache.Add(id, entity)
Another is to coerce the type - this isn't relevant here, but it may come to be useful down the road:
Cache.Add((entity :> SourceEntity).id, entity)
I'd recommend this article from F# for fun and profit on type inference for a nice explanation of the process.
P.S.
You actually only needed that one type annotation.
The rest can be inferred :)
module Source =
let Cache = new Collections.Generic.Dictionary<_, _>()
let AddToCache (entity: SourceEntity) =
Cache.Add(entity.id, entity)
()
let AddRec () =
let ent = {
id = 1
link = ""
}
AddToCache(ent)

How to receive a type that extends an interface without losing the original type

I have just started using F# and my brain is broken trying to figure out how to work with its types without having to resort to an OO type of programming.
Here is my situation I basically want to create a method where I provide the type and the Id and it returns to me the object on the database.
So basically this is what I get so far.
let client = MongoClient()
let database = client.GetDatabase("testdb")
let lowerCase (str : string) =
str.ToLower()
let nameOf (classType: Type) =
classType.Name
let nameTypeOf<'a> =
nameOf typeof<'a>
let getCollection<'a> =
let collectionName = nameTypeOf<'a> |> lowerCase
database.GetCollection<'a> collectionName
let dbSelect<'a> id =
let collection = getCollection<'a>
collection.Find(fun(x) -> x.Id = id).First()
So my problem is with the dbSelect, obviously it does not compile since x is generic, basically I wanted to create an interface with the Id and all my objects interface with it.
I do know how to do it using classes and inheritances, but I am avoiding having to use instanced classes outside interop with c# libraries. What would be the best functional way to do it, if there is any.
This is what I was eexpecting to call it with
type IDbObject =
abstract Id: string
type Item =
{
Id: string
Name: string
}
interface IDbObject with
member x.Id = x.Id
let item =
selectDb<Item> "5993592a35ce962b80da1e22"
Any help would be appreciated.
And if anyone want to point out how crappy my code is, any feedback is really appreciated
I don't think the solution here is much different from what you'd have in C#. You can constrain the generic type to use the interface members, getting something roughly like this:
let getCollection<'a when 'a :> IDbObject> () =
let collectionName = nameTypeOf<'a> |> lowerCase
database.GetCollection<'a> collectionName
let dbSelect<'a when 'a :> IDbObject> id =
let collection = getCollection<'a>()
collection.Find(fun (x : 'a) -> x.Id = id).First()
The type of dbSelect should be inferred to be string -> #IDbObject, and be coerced to string -> 'a at the call site.

Newtonsoft cannnot deserialize an empty F# sequence?

Using Newtonsoft.Json, latest version (=6.0.6) I get the following error:
Cannot create and populate list type Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers+EmptyEnumerable`1[System.String]
However in a post it was announced that Newtonsoft.Json would fully support Fsharp types?
When I change the offending type to a regular array, everything works fine.
The code:
type Prescription () =
member val Id = "" with get, set
member val Status = new PrescriptionStatus() with get, set
member val Prescriber = new Prescriber() with get, set
member val Indications = [||] : string[] with get, set
When I change Indications to be:
member val Indications = Seq.empty : string seq with get, set
I run into the error.
Also, when I initialise what is in fact an enumerable as an array, it cannot be constructed:
member val Indications : string seq = [||] |> Array.toSeq with get, set
I guess the answer is, Newtonsoft.Json doesn't fully support F# types.
But F# doesn't make supporting them particularly easy. For instance, an empty seq defined with Seq.empty is not just an IEnumerable<T>, it's a particular enumerable implementation EmptyEnumerable<T>, and this seems to throw off serialization - most likely because there's no appropriate constructor on it. From the post you linked to:
To all future creators of immutable .NET collections: If your collection of T has a constructor that takes IEnumerable then Json.NET will automatically work when deserializing to your collection, otherwise you're all out of luck.
If you initialize your seq like this instead, perhaps the behaviour will be different:
member val Indications = Seq.ofArray [||] : string seq with get, set
But that's splitting hairs, the actual answer here is simple - don't serialize seqs. Just use concrete, well-behaved types like arrays. The simpler the type, the less likely it is to give you headaches when doing serialization or interop.
Setting JsonSerializerSettings.ObjectCreationHandling = ObjectCreationHandling.Replace will fix this error.
I liked the notion of using simple types like arrays, only I wanted to use the same DTOs also for mapping to IQueryable in Linq queries. So, in that respect, arrays were not an option.
Luckily with some testing, it was simple:
#load ".\Scripts\load-project.fsx"
#time
open System
open System.Collections.Generic
open Newtonsoft.Json
[<CLIMutable>]
type Test1 =
{
Strings : string seq
}
type Test2 () =
member val Strings = Seq.empty : IEnumerable<string> with get, set
type Test3 () =
member val Strings = Seq.empty : String seq with get, set
type Test4 () =
member val Strings : IEnumerable<string> = Seq.empty : IEnumerable<string> with get, set
type Test5 () =
member val Strings : IEnumerable<string> = [] |> List.toSeq : IEnumerable<string> with get, set
type Test6 () =
member val Strings = [] |> List.toSeq : string seq with get, set
let test1 = { Strings = Seq.empty }
let test2 = new Test2 ()
let test3 = new Test3 ()
let test4 = new Test4 ()
let test5 = new Test5 ()
let test6 = new Test6 ()
let json1 = JsonConvert.SerializeObject(test1)
let json2 = JsonConvert.SerializeObject(test2)
let json3 = JsonConvert.SerializeObject(test3)
let json4 = JsonConvert.SerializeObject(test4)
let json5 = JsonConvert.SerializeObject(test5)
let json6 = JsonConvert.SerializeObject(test6)
let deserialized1 = JsonConvert.DeserializeObject<Test1>(json1) // Fails
let deserialized2 = JsonConvert.DeserializeObject<Test2>(json2) // Fails
let deserialized3 = JsonConvert.DeserializeObject<Test3>(json3) // Fails
let deserialized4 = JsonConvert.DeserializeObject<Test4>(json4) // Fails
let deserialized5 = JsonConvert.DeserializeObject<Test5>(json5) // Passes
let deserialized6 = JsonConvert.DeserializeObject<Test5>(json6) // Passes
So, as long as you construct your sequence using a type that has a recognisable constructor, for example a list, the object can be deserialised. Strangely enough, initializing the sequence as an array and then converting it to a sequence, like with the list to sequence example (which passes), fails.

Casting an object to a generic type without supplying the parameters

Say I have a code like this
let a = new List<int>()
let b = a :> obj :?> List<obj>
It throws an exception saying that it can't do it since one is List<int> while I'm trying to make it an List<obj>.
I understand why that's a problem. It can't just magically create an interface for me that replaces all int types with obj, but what CAN I do here?
I have an object and I know that's it's a List of something. How can I access the elements and just not care about their type?
My concrete example doesn't use Lists so I require a general, not a List specific, solution.
In case of lists you can use System.Collections.IList to access elements
open System.Collections
open System.Collections.Generic
let x = List<int>()
let y: IList = downcast (x :> obj)
This approach can also be generalized: make your generic classes implement non-generic interface:
type IT =
abstract Value: obj
type T<'a>(a: 'a) =
member val Value = a;
interface IT with
member this.Value = upcast this.Value
If this is not an option (i.e. because you cannot make changes in classes) you can always resort to reflection
type T<'a>(a: 'a) =
member val Value = a;
type Action =
static member Do(a: T<_>) = printfn "%A" a.Value
let v = T(10)
let mi = typeof<Action>.GetMethod("Do").MakeGenericMethod(v.GetType().GetGenericArguments().[0])
mi.Invoke(null, [|v|])

Resources