Newtonsoft (json.net) 反序列化可以在 F# 中变得懒惰吗?

Can Newtonsoft (json.net) Deserialize be made lazy in F#?

考虑以下代码,它使用 FSharp.Data 从 Web 资源请求数据

let resp = Http.RequestStream(url, headers, query)
use rdr = new StreamReader(resp.ResponseStream)
use jrdr = new JsonTextReader(rdr)
let serializer = new JsonSerializer()
let myArray = serializer.Deserialize<someType[]>(jrdr).Value

myArraysomeType的数组。数组被急切地评估,所以如果我请求大量数据,我会预先消耗大量 RAM。

如果我要求 json.net 给我一个序列呢?

let resp = Http.RequestStream(url, headers, query)
use rdr = new StreamReader(resp.ResponseStream)
use jrdr = new JsonTextReader(rdr)
let serializer = new JsonSerializer()
let mySeq = serializer.Deserialize<someType seq>(jrdr).Value

如果我遍历 mySeq 并将其写入文本文件,是否所有内容都从流中拉出并延迟反序列化?还是要求 json.net 反序列化的行为会强制在此时急切地评估所有内容?

更新

根据 dbc 的公认答案,功能性惰性函数将类似于以下内容

let jsonSeqFromStream<'T>(stream:Stream) = seq{
    let serializer = JsonSerializer.CreateDefault()
    use rdr = new StreamReader(stream, Encoding.UTF8, true, 4096, true)
    use jrdr = new JsonTextReader(rdr, CloseInput = false)
    let rec resSeq inArray = seq{
        if jrdr.Read() then
            match jrdr.TokenType with
            |JsonToken.Comment -> yield! resSeq inArray
            |JsonToken.StartArray when not inArray -> yield! resSeq true
            |JsonToken.EndArray when inArray -> yield! resSeq false
            |_ ->
                let resObj = serializer.Deserialize<'T>(jrdr)
                yield resObj
                yield! resSeq inArray
        else
            ()
    }
    yield! resSeq false
}

Json.NET 序列的反序列化可以变得惰性化,但不是那么自动。相反,您必须将 or 中的答案之一修改为 f#.

要确认序列的反序列化不是默认惰性的,定义以下函数:

let jsonFromStream<'T>(stream : Stream) =
    Console.WriteLine(typeof<'T>) // Print incoming type for debugging purpose
    let serializer = JsonSerializer.CreateDefault()
    use rdr = new StreamReader(stream, Encoding.UTF8, true, 4096, true)
    use jrdr = new JsonTextReader(rdr, CloseInput = false)
    let res = serializer.Deserialize<'T>(jrdr)
    Console.WriteLine(res.GetType()) // Print outgoing type for debugging purpose
    res

然后如果我们有一些流 stream 包含一个 JSON 对象数组 someType,并像这样调用方法:

let mySeq = jsonFromStream<someType seq>(stream)

然后生成以下调试输出:

System.Collections.Generic.IEnumerable`1[Oti4jegh9906+someType]
System.Collections.Generic.List`1[Oti4jegh9906+someType]

如您所见,从 .Net 的角度来看,用 someType seq 调用 JsonSerializer.Deserialize<T>() 与在 c# 中用 IEnumerable<someType> 调用它是一样的,在这样的case Json.NET 将结果具体化并 return 将其作为 List<someType>.

演示 fiddle #1 here.

要将 JSON 数组解析为惰性序列,您需要手动创建一个 seq 函数,该函数用 JsonReader.Read() 遍历 JSON 并反序列化和产生每个数组条目:

let jsonSeqFromStream<'T>(stream : Stream) =
    seq {
        // Adapted from this answer 
        // To 
        let serializer = JsonSerializer.CreateDefault()
        use rdr = new StreamReader(stream, Encoding.UTF8, true, 4096, true)
        use jrdr = new JsonTextReader(rdr, CloseInput = false)
        let inArray = ref false
        while jrdr.Read() do
            if (jrdr.TokenType = JsonToken.Comment) then
                ()
            elif (jrdr.TokenType = JsonToken.StartArray && not !inArray) then
                inArray := true
            elif (jrdr.TokenType = JsonToken.EndArray && !inArray) then
                inArray := false
            else
                let res = serializer.Deserialize<'T>(jrdr)
                yield res
    }

(因为跟踪我们是否正在解析数组值是有状态的,所以这看起来不太实用。也许它可以做得更好?)

这个函数的return可以这样使用,例如:

let mySeq = jsonSeqFromStream<someType>(stream)

mySeq |> Seq.iter (fun (s) -> printfn "%s" (JsonConvert.SerializeObject(s)))

演示 fiddle #2 here.