我正在将一些(经过一段时间的测试(代码从C#移植到F#,在F#中运行时遇到了一些问题
C#代码:
(我想串行化的对象(
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转换器(
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();
}
}
(尝试F#代码(
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)
以及我对模型的尝试(你可以看到有几次尝试被评论掉了(。
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
}
由于您的转换器似乎源于Brian Rogers对如何使用JSON.net处理同一属性的单个项和数组的回答,我假设您正在尝试为类型为System.Collections.Generic.List<'T>
(在FSharpx.Collections
中缩写为ResizeArray
(的可变列表创建一个通用转换器。
F#还具有一个不可变列表类型FSharp.Collections.List<'T>
,缩写为list
。确定要使用哪一个[1]
考虑到这一点,假设你想要System.Collections.Generic.List<'T>
,你的转换器可以写如下:
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://learn.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())
您的Localities
类型定义如下:
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>
}
在这里演示小提琴#1。
如果你确实想使用f#的不可变列表,那么定义你的转换器如下:
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://learn.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())
并修改Localities
如下:
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>
}
在这里演示小提琴#2。
更新
实际上不需要将JSON预加载到JToken
中来检查它是否是一个数组,只需检查JsonReader.TokenType
的值即可。以下版本的SingleOrArrayConverter<'T>
可以做到这一点,因此应该更高效:
module JsonExtensions =
type JsonReader with
member r.ReadAndAssert() =
if not (r.Read()) then raise (JsonReaderException("Unexpected end of JSON stream."))
r
member r.MoveToContentAndAssert() =
if r.TokenType = JsonToken.None then r.ReadAndAssert() |> ignore
while r.TokenType = JsonToken.Comment do r.ReadAndAssert() |> ignore
r
open JsonExtensions
type SingleOrArrayConverter<'T>() =
inherit JsonConverter()
override this.CanConvert(objectType) = objectType = typeof<ResizeArray<'T>>
override this.ReadJson(reader, objectType, existingValue, serializer) =
let a = if (existingValue :? ResizeArray<'T>) then existingValue :?> ResizeArray<'T> else ResizeArray<'T>() // Reuse the incoming List<T> if preallocated
match reader.MoveToContentAndAssert().TokenType with
| JsonToken.StartArray -> serializer.Populate(reader, a)
| _ -> a.Add(serializer.Deserialize<'T>(reader))
a :> obj
override this.CanWrite = false // Simplified syntax
override this.WriteJson(writer, value, serializer) = raise (new NotImplementedException())
演示小提琴#3在这里。
[1]有关详细信息,请参阅创建通用列表<T>在F#中。