F#与C#中的反序列化

我有以下json:

_<spyOn> : could not find an object to spy upon

最后3个字段是可选的。

在C#中反序列化时,请执行以下操作:

{
  "data": [
    {
      "timestamp": "2019-11-07T00:23:52.095Z","symbol": "XBTUSD","side": "Buy","size": 1,"price": 9356.5,"tickDirection": "PlusTick","trdMatchID": "01476235-ad89-1777-9067-8ce6d0e29992","grossValue": 10688,"homeNotional": 0.00010688,"foreignNotional": 1
    }
  ]
}

这一切都很好。

在F#中,我这样做:

public class Trade
{
    public DateTime Timestamp;
    public string Symbol;
    public string Side;
    public long Size;
    public long Price;
    public long? GrossValue;
    public float? HomeNotional;
    public float? ForeignNotional;
}

public class TradeContainer
{
    public Trade[] Data;
}

var j = JsonConvert.DeserializeObject<TradeContainer>(x);

,但是会失败。但是,如果我删除了交易类型中的option关键字,那么它将正确地反序列化。

我得到的错误是:

  

Newtonsoft.Json.JsonSerializationException:意外的属性   阅读工会时发现“ homeNotional”。路径“数据[0] .homeNotional”,   第1行,位置233。在   Newtonsoft.Json.Converters.DiscriminatedUnionConverter.ReadJson(JsonReader   读取器,类型objectType,对象existvalue,JsonSerializer   序列化器)

为什么有区别?但我又该如何实现呢,因为我需要计划一些值可能不存在?

blzhi 回答:F#与C#中的反序列化

问题出在JSON.NET的DU序列化实现中。 It's not idiomatic,实际上是转储案例和字段:

type Shape =
    | Rectangle of width : float * length : float
    | Circle of radius : float
    | Empty

[<EntryPoint>]
let main argv = 

    let shape1 = Rectangle(1.3,10.0)

    let json = JsonConvert.SerializeObject(shape1)
    // {
    //   "Case": "Rectangle",//   "Fields": [
    //     1.3,//     10.0
    //   ]
    // }

解串器期望相同的结构。

艾萨克·亚伯拉罕(Isaac Abraham)创建了an idiomatic custom converter,应该改用它:

let settings = new JsonSerializerSettings()
settings.Converters.Add(IdiomaticDuConverter())

let t = JsonConvert.DeserializeObject<TradeContainer>(json,settings)

IdiomaticDuConverter的代码是:

namespace Newtonsoft.Json.Converters

open Microsoft.FSharp.Reflection
open Newtonsoft.Json
open System

type IdiomaticDuConverter() = 
    inherit JsonConverter()

    [<Literal>]
    let discriminator = "__Case"
    let primitives = Set [ JsonToken.Boolean; JsonToken.Date; JsonToken.Float; JsonToken.Integer; JsonToken.Null; JsonToken.String ]

    let writeValue (value:obj) (serializer:JsonSerializer,writer : JsonWriter) =
        if value.GetType().IsPrimitive then writer.WriteValue value
        else serializer.Serialize(writer,value)

    let writeProperties (fields : obj array) (serializer:JsonSerializer,writer : JsonWriter) = 
        fields |> Array.iteri (fun index value -> 
                      writer.WritePropertyName(sprintf "Item%d" index)
                      (serializer,writer) |> writeValue value)

    let writeDiscriminator (name : string) (writer : JsonWriter) = 
        writer.WritePropertyName discriminator
        writer.WriteValue name

    override __.WriteJson(writer,value,serializer) = 
        let unionCases = FSharpType.GetUnionCases(value.GetType())
        let unionType = value.GetType()
        let case,fields = FSharpValue.GetUnionFields(value,unionType)
        let allCasesHaveValues = unionCases |> Seq.forall (fun c -> c.GetFields() |> Seq.length > 0)

        match unionCases.Length,fields,allCasesHaveValues with
        | 2,[||],false -> writer.WriteNull()
        | 1,[| singleValue |],_
        | 2,false -> (serializer,writer) |> writeValue singleValue
        | 1,false -> 
            writer.WriteStartObject()
            (serializer,writer) |> writeProperties fields
            writer.WriteEndObject()
        | _ -> 
            writer.WriteStartObject()
            writer |> writeDiscriminator case.Name
            (serializer,writer) |> writeProperties fields
            writer.WriteEndObject()

    override __.ReadJson(reader,destinationType,_,_) = 
        let parts = 
            if reader.TokenType <> JsonToken.StartObject then [| (JsonToken.Undefined,obj()),(reader.TokenType,reader.Value) |]
            else 
                seq { 
                    yield! reader |> Seq.unfold (fun reader -> 
                                         if reader.Read() then Some((reader.TokenType,reader.Value),reader)
                                         else None)
                }
                |> Seq.takeWhile(fun (token,_) -> token <> JsonToken.EndObject)
                |> Seq.pairwise
                |> Seq.mapi (fun id value -> id,value)
                |> Seq.filter (fun (id,_) -> id % 2 = 0)
                |> Seq.map snd
                |> Seq.toArray

        let values = 
            parts
            |> Seq.filter (fun ((_,keyValue),_) -> keyValue <> (discriminator :> obj))
            |> Seq.map snd
            |> Seq.filter (fun (valueToken,_) -> primitives.Contains valueToken)
            |> Seq.map snd
            |> Seq.toArray

        let case = 
            let unionCases = FSharpType.GetUnionCases(destinationType)
            let unionCase =
                parts
                |> Seq.tryFind (fun ((_,_) -> keyValue = (discriminator :> obj))
                |> Option.map (snd >> snd)
            match unionCase with
            | Some case -> unionCases |> Array.find (fun f -> f.Name :> obj = case)
            | None ->
                // implied union case
                match values with
                | [| null |] -> unionCases |> Array.find(fun c -> c.GetFields().Length = 0)
                | _ -> unionCases |> Array.find(fun c -> c.GetFields().Length > 0)

        let values = 
            case.GetFields()
            |> Seq.zip values
            |> Seq.map (fun (value,propertyInfo) -> Convert.ChangeType(value,propertyInfo.PropertyType))
            |> Seq.toArray

        FSharpValue.MakeUnion(case,values)

    override __.CanConvert(objectType) =
        FSharpType.IsUnion objectType &&
        not (objectType.IsGenericType &&
             typedefof<list<_>> = objectType.GetGenericTypeDefinition())
本文链接:https://www.f2er.com/3144812.html

大家都在问