I'm caling an API using Flurl.
//# models.fs
module models =
type Ticker = { Ask :decimal; Bid :decimal; Last: decimal; High :decimal; Timestamp :int; }
//# Client.fs
namespace MyLibrary
// ... some code
url.GetJsonAsync<models.Ticker>()
This works and I can access the ticker.Ask property.
The class models.Ticker is visible from another C# project and the construcor is this:
public Ticker(decimal ask, decimal bid, decimal last, decimal high, int timestamp);
I don't want expose the models module and the Ticker class/record so I changed the visibility to internal:
# models.fs
module internal models =
type Ticker = { Ask :decimal; Bid :decimal; Last: decimal; High :decimal; Timestamp :int; }
The code still "compile" but when I run it I have this exception:
- Newtonsoft.Json.JsonSerializationException: Unable to find a constructor to use for type MyProject.models+Ticker. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'high', line 1, position 8. at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue) at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent) at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType) at Newtonsoft.Json.JsonSerializer.Deserialize[T](JsonReader reader) at Flurl.Http.Configuration.NewtonsoftJsonSerializer.Deserialize[T](Stream stream) at Flurl.Http.HttpResponseMessageExtensions.ReceiveJson[T](Task`1 response)} System.Exception {Newtonsoft.Json.JsonSerializationException}*
This is the full code example, I'm new with F#, maybe needed to explain the problem:
open Flurl.Http
// open mynamespace where is models
type IClient =
abstract member GetAsk : decimal
type MyClient() =
let getTicker () =
let url = "https://www.bitstamp.net/api/v2/ticker/XRPUSD"
url.GetJsonAsync<models.Ticker>()
interface IClient with
member __.GetAsk =
let ticker = getTicker().Result
ticker.Ask
I'm using the Client from a C# project. I imagine the constructor of Ticker changed because of the visibility. But, why? How can I maintain the Ticker class hidden and make the code works as expected?
[Update: use a class]
Using a class
type internal Ticker(ask:decimal, bid :decimal, last: decimal, high :decimal, timestamp :int) =
member this.Ask = ask
does the job as expected.
If internal it is not visible externally but it is "usable" by the JsonDeserializer.
I'm still confused for the behavior when I use a Record.

Your problem is that essentially all .NET serializers including Json.NET,
XmlSerializer, System.Text.Json and so on are designed to serialize the public members of types -- even if the type itself is non-public 1. However, as explained in the F# documentation Rules for Access Control:Thus the fields of an internal record are not public. And there is no way to make them public according to the issue Fields and methods on internal types are impossible to make public #2820.
To work around this, you are going to need to create a custom contract resolver that, for any nonpublic F# record type, considers all record fields to be public. In addition it will be necessary to invoke the constructor manually because the constructor will also have been marked as nonpublic.
The following contract resolver does the job:
Then to manually serialize and deserialize
models.Ticker, you would set up yourJsonSerializerSettingsas follow:And to configure Flurl.Http with custom settings see their documentation page Configuration : Serializers, which, when translated to F#, should look something like:
Notes:
RecordContractResolveronly modifies the serialization of nonpublic F# record types. Contracts for all other types are unchanged.Newtonsoft recommends to cache contract resolvers for best performance.
I tested the contract resolver standalone, but could not test it with Flurl. The Flurl code was written against Flurl.Http version 3.2.4 which is the latest released version at the time of writing this answer.
Flurl.Http 4.0 is set to replace Json.NET with System.Text.Json so you will have the same problem all over again if you upgrade. And unfortunately System.Text.Json's more limited contract customization in .NET 7 does not allow for customization of parameterized constructors, so it may prove even more difficult to deserialize internal F# records in that release.
Demo fiddle here.
1 One notable exception to this is the deprecated
BinaryFormatterwhich serializes all fields whether public or private. However, according to the docs: