I am using the Flurl library to call a webservice, which returns a JSON
{"data":{"charges":[{"code":30200757,"reference":"","dueDate":"18/12/2018","checkoutUrl":"https://sandbox.boletobancario.com/boletofacil/checkout/C238E9C42A372D25FDE214AE3CF4CB80FD37E71040CBCF50","link":"https://sandbox.boletobancario.com/boletofacil/charge/boleto.pdf?token=366800:m:3ea89b5c6579ec18fcd8ad37f07d178f66d0b0eb45d5e67b884894a8422f23c2","installmentLink":"https://sandbox.boletobancario.com/boletofacil/charge/boleto.pdf?token=30200757:10829e9ba07ea6262c2a2824b36c62e7c5782a43c855a1004071d653dee39af0","payNumber":"BOLETO TESTE - Não é válido para pagamento","billetDetails":{"bankAccount":"0655/46480-8","ourNumber":"176/30200757-1","barcodeNumber":"34192774200000123001763020075710655464808000","portfolio":"176"}}]},"success":true}
This is my F# code:
let c = "https://sandbox.boletobancario.com/boletofacil/integration/api/v1/"
.AppendPathSegment("issue-charge")
.SetQueryParams(map)
.GetJsonAsync()
c.Wait()
let j = c.Result
let success = j?success
I checked and the variable j contains an obj ("System.Dynamic.ExpandoObject")
How can I access, for example, the success value of this obj in variable j?
And how to access the data ?
I don't have experience with that particular library, but if the result is just a normal ExpandoObject, then the following should do the trick.
First of all, ExpandoObject implements the IDictionary<string, obj>, so you can cast the value to IDictionary and then add or get members as you wish:
open System.Dynamic
open System.Collections.Generic
let exp = ExpandoObject()
// Adding and getting properties using a dictionary
let d = exp :> IDictionary<string, obj>
d.Add("hi", 123)
d.["hi"]
If you want to use the ? syntax, you can define the ? operator yourself, doing exactly the same as above:
let (?) (exp:ExpandoObject) s =
let d = exp :> IDictionary<string, obj>
d.[s]
exp?hi
That said, if you can use type providers, it'd be a lot easier to do this with F# Data for the JSON parsing, because then you could just replace all the dynamic unsafe ? accesses with type-checked ones!
You can use a predefined ? operator for all your DynamicObject interop needs with fsprojects/FSharp.Interop.Dynamic
open FSharp.Interop.Dynamic
let ex1 = ExpandoObject()
ex1?Test<-"Hi"//Set Dynamic Property
ex1?Test //Get Dynamic
Related
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.
I am using record types in a F# project that I am exposing to a C# WebApi project. For example:
type Account = {Amount:float; Number:int; Holder:string}
Based on this post and this post, the json is serializaing correctly.
{"Amount":100.0,"Number":1,"Holder":"Homer"}
However, when I add in a option type to the record,
type Account = {Amount:float; Number:int; Holder:string option }
the json becomes unglued.
{"Amount":100.0,"Number":1,"Holder":{"Case":"Some","Fields":["Homer"]}}
I would want the json to look the same as the non-option type record with the serializer being smart enough to take the values and put them in/out of the option type automatically.
Has anyone built a custom formatter to this end? Is there something OOB that I am missing?
Thanks
I tried the converter linked in the other answer, and I didn't like the output of other DUs which were not Option. Instead of that, you may want to opt into only changing the behavior on the Option type rather than all DUs.
I found this converter that will change only the behavior for option type to render null on the None option, otherwise the value. The original code/author info can be found here.
open System
open Microsoft.FSharp.Reflection
open Newtonsoft.Json
open Newtonsoft.Json.Converters
type OptionConverter() =
inherit JsonConverter()
override x.CanConvert(t) =
t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<option<_>>
override x.WriteJson(writer, value, serializer) =
let value =
if value = null then null
else
let _,fields = FSharpValue.GetUnionFields(value, value.GetType())
fields.[0]
serializer.Serialize(writer, value)
override x.ReadJson(reader, t, existingValue, serializer) =
let innerType = t.GetGenericArguments().[0]
let innerType =
if innerType.IsValueType then (typedefof<Nullable<_>>).MakeGenericType([|innerType|])
else innerType
let value = serializer.Deserialize(reader, innerType)
let cases = FSharpType.GetUnionCases(t)
if value = null then FSharpValue.MakeUnion(cases.[0], [||])
else FSharpValue.MakeUnion(cases.[1], [|value|])
Using the converter is the same as other answer:
let json = JsonConvert.SerializeObject(myObj, new OptionConverter())
A custom Json.NET converter that handles option types and single-type discriminated unions does exist (or at least claims to, I only tested the option type case). It can be found here.
Usage:
let act = {Amount= 100.0; Number= 1; Holder= Some "Homer"}
let json = JsonConvert.SerializeObject(act, new IdiomaticDuConverter())
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|])
I have an array and I want to cast it to an object. This is my code.
let a = [| 1 |]
let b = a :?> obj
but it tells me that int [] has no proper subtypes and cannot be used as a source of runtime type coercion. I'm pretty sure I can always do (object) in C# without the compiler complaining, so what's the issue here?
You're trying to downcast (:?>), instead of upcast (:>). Your code should be:
let a = [| 1 |]
let b = a :> obj
Please see http://msdn.microsoft.com/en-us/library/dd233220.aspx for more details.
While ebb's answer is of course correct, there is another solution for the special case of upcasting to obj, which is the box operator:
let a = [| 1 |]
let b = box a
In F#, box doesn't have the same meaning as it does in the CLR, where it means "make a reference-type object corresponding to a value-type value." In F#, it just means "cast to obj." Because of this, you can use box with reference types as well as with value types.
Here is what am I trying to do :
and [<AbstractClass>] Figure() =
let mutable name : string = ""
let mutable color : ColorType = WHITE
abstract member Name : string
member F.Color
with get() = color
and set v = color <- v
abstract member motion : Dashboard -> SPosition -> ColorType -> list<SPosition * CellDashboard * bool>;
override F.ToString() = name
and CellDashboard(addingFigure : Nullable<Figure>) as C =
let mutable attacked : bool = false;
let mutable cleaned : bool = false;
let mutable _CellState : Nullable<Figure> = Nullable<Figure>(addingFigure);
the trouble is :
Error 1 A generic construct requires that the type 'Figure' be non-abstract
why ? And how can I avoid this error ?
The Nullable<T> type can only be used for .NET value types. The error message essentially means that the compiler cannot check whether the constraint holds or not (because the type is abstract).
If you want to represent missing value in F#, it is better to use option<T> instead.
If you're writing code that will be used from C#, then it is better to avoid exposing F#-specific types (like option<T>) and you can mark the type with AllowNullLiteral, which makes it possible to pass null as a value of the type (but null is evil, so this shouldn't be done unless necessary!)