Updated below ...
I recently started experimenting with ServiceStack in F #, so naturally, I started by porting the Hello World example :
open ServiceStack.ServiceHost open ServiceStack.ServiceInterface open ServiceStack.WebHost.Endpoints [<CLIMutable; Route("/hello"); Route("/hello/{Name}")>] type Hello = { Name : string } [<CLIMutable>] type HelloResponse = { Result : string } type HelloService() = inherit Service() member x.Any(req:Hello) = box { Result = sprintf "Hello, %s!" req.Name } type HelloAppHost() = inherit AppHostBase("Hello Web Services", typeof<HelloService>.Assembly) override x.Configure container = () type Global() = inherit System.Web.HttpApplication() member x.Application_Start() = let appHost = new HelloAppHost() appHost.Init()
This works great. It is very concise, easy to work, I love it. However, I noticed that the routes defined in the sample allow us to include the Name parameter. Of course Hello, ! looks lame as a conclusion. I could use String.IsNullOrEmpty , but idiomatic in F # to be explicit about things that are optional, using the Option type. So I changed my Hello type to see what happens:
[<CLIMutable; Route("/hello"); Route("/hello/{Name}")>] type Hello = { Name : string option }
As soon as I did this, a system like F # made me cope with the fact that Name might not matter, so I changed HelloService to this so that everything HelloService :
type HelloService() = inherit Service() member x.Any(req:Hello) = box { Result = match req.Name with | Some name -> sprintf "Hello, %s!" name | None -> "Hello!" }
This compiles and works fine when I do not provide a Name parameter. However, when I provide the name ...
KeyValueDataContractDeserializer: Convert errors to type: Type definitions should start with '{', waiting for the serialized type 'FSharpOption`1', got a line starting with: World
Of course, this was not a complete surprise, but this brings me to my question:
It would be trivial for me to write a function that can wrap an instance of type T into an instance of type FSharpOption<T> . Are there hooks in ServiceStack that would allow me to provide such a function for use during deserialization? I looked, but I could not find, and I hope that I was just looking for the wrong place.
This is more important for using F # than it might seem at first glance, because the classes defined in F # are not null by default. Thus, the only (satisfactory, non-hacky) way to have one class as an optional property of another class is, you guessed it, the Option type.
Update:
I managed to sort the work by making the following changes:
In the ServiceStack source, I made this type public: ServiceStack.Text.Common.ParseFactoryDelegate
... and I also made this field open: ServiceStack.Text.Jsv.JsvReader.ParseFnCache
With these two things public, I was able to write this code in F # to modify the ParseFnCache dictionary. I had to run this code before instantiating my AppHost - it did not work if I ran it inside the AppHost Configure method.
JsvReader.ParseFnCache.[typeof<Option<string>>] <- ParseFactoryDelegate(fun () -> ParseStringDelegate(fun s -> (if String.IsNullOrEmpty s then None else Some s) |> box))
This works for my original test case, but besides the fact that I had to make fragile changes for the internal components of ServiceStack, it sucks because I have to do this once for each type that I want Option<T> .
What would be better if I could do it in a general way. In terms of C #, it would be great if I could provide ServiceStack a Func<T, Option<T>> , and ServiceStack when deserializing a property whose general type definition matches the type of my function returned, deserialize T , and then pass the result to my function.
Something like this would be surprisingly convenient, but I could live with a once-per-wrapper approach if it was actually part of the ServiceStack, and not my ugly hack that probably broke something somewhere still.