I am porting some ( well tested over time ) code from C# to F# and having some issues getting something to work in F#
The C# Code:
( Object I want to serialise )
public class Locality
{
public string category { get; set; }
public int id { get; set; }
public string location { get; set; }
public string postcode { get; set; }
public string state { get; set; }
public double? latitude { get; set; }
public double? longitude { get; set; }
}
public class Localities
{
[JsonProperty("locality")]
[JsonConverter(typeof(R2H.Models.JSon.SingleOrArrayConverter<Locality>))]
public List<Locality> locality { get; set; }
}
public class AuspostPostCodeLocality
{
public Localities localities { get; set; }
}
( JSON converter )
public class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
( Attempt at F# Code )
type SingleOrArrayConverter<'T>() =
inherit JsonConverter()
override this.CanConvert(objectType : Type) =
objectType = typeof<List<'T>>
override this.ReadJson(reader : JsonReader, objectType : Type, existingValue : System.Object, serializer : JsonSerializer) =
let mutable (token : JToken) = JToken.Load (reader)
if token.Type = JTokenType.Array then
(token.ToObject<List<'T>> ())
else
([|(token.ToObject<'T> ())|])
override this.CanWrite
with get() =
false
override this.WriteJson(writer : JsonWriter, value : System.Object, serializer : JsonSerializer) =
raise (new NotImplementedException() :> System.Exception)
And my attempt at the model ( You can see several attempts commented out ).
type Locality = {
category: string
id: int
location: string
postcode: int
state: string
latitude: decimal
longitude: decimal
}
type Localities = {
//inherit JsonConverter<SingleOrArrayConverter<Locality>>()
//[<JsonProperty("locality");JsonConverter<SingleOrArrayConverter<Locality>>>]
//[<JsonConverter(typeof (SingleOrArrayConverter<Locality>))>]
//[<JsonProperty("locality")>]
locality: List<Locality>
}
type PostCodeLocality = {
localities : Localities
}
CodePudding user response:
Since your converter seems to have originated from this answer by Brian Rogers to How to handle both a single item and an array for the same property using JSON.net, I assume that you are trying to create a generic converter for mutable lists of type System.Collections.Generic.List<'T>
(abbreviated to ResizeArray
in FSharpx.Collections
).
F# also has an immutable list type FSharp.Collections.List<'T>
abbreviated to list
. Be sure which one you want to use.[1]
With that in mind, assuming you want System.Collections.Generic.List<'T>
your converter can be written as follows:
type SingleOrArrayConverter<'T>() =
inherit JsonConverter()
override this.CanConvert(objectType) = objectType = typeof<ResizeArray<'T>>
override this.ReadJson(reader, objectType, existingValue, serializer) = // Unlike in C# it's not necessary to declare the types of the arguments when there is no ambiguity
let token = JToken.Load (reader)
if token.Type = JTokenType.Array then
// Upcast to obj as upcasting is not automatic for returned value
// https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/casting-and-conversions#upcasting
(token.ToObject<ResizeArray<'T>> ()) :> obj
else
ResizeArray [|(token.ToObject<'T> ())|] :> obj // Convert array to List<T> then upcast to object
override this.CanWrite = false // Simplified syntax
override this.WriteJson(writer, value, serializer) = raise (new NotImplementedException())
And your Localities
type defined as follows:
type Localities = {
[<JsonProperty("locality")>]
[<JsonConverter(typeof<SingleOrArrayConverter<Locality>>)>] // Fix syntax: typeof<'T> not typeof(T)
locality: ResizeArray<Locality> // Be sure whether you want System.Collections.Generic.List<'T> a.k.a ResizeArray<'T> or FSharp.Collections.List<'T>
}
Demo fiddle #1 here.
If you do want to use f#'s immutable list, define your converter as follows:
type SingleOrArrayFSharpListConverter<'T>() =
inherit JsonConverter()
override this.CanConvert(objectType) = objectType = typeof<list<'T>>
override this.ReadJson(reader, objectType, existingValue, serializer) = // Unlike in C# it's not necessary to declare the types of the arguments when there is no ambiguity
let token = JToken.Load (reader)
if token.Type = JTokenType.Array then
// Upcast to obj as upcasting is not automatic for returned value
// https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/casting-and-conversions#upcasting
(token.ToObject<list<'T>> ()) :> obj
else
[token.ToObject<'T>()] :> obj // Convert array to list<T> then upcast to object
override this.CanWrite = false // Simplified syntax
override this.WriteJson(writer, value, serializer) = raise (new NotImplementedException())
And modify Localities
as follows:
type Localities = {
[<JsonProperty("locality")>]
[<JsonConverter(typeof<SingleOrArrayFSharpListConverter<Locality>>)>] // Fix syntax: typeof<'T> not typeof(T)
locality: Locality list // Be sure whether you want System.Collections.Generic.List<'T> a.k.a ResizeArray<'T> or FSharp.Collections.List<'T>
}
Demo fiddle #2 here.
[1] For details see Creating a generic List <T> in F#.