Может ли Newtonsoft (json.net) десериализоваться быть ленивым в F #? - PullRequest
1 голос
/ 30 апреля 2019

Рассмотрим следующий код, который использует FSharp.Data для запроса данных с веб-ресурса

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

myArray - это массив someType. Массивы оцениваются с нетерпением, поэтому, если я запрашиваю большой объем данных, я буду использовать большой объем оперативной памяти.

Что если я попрошу 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 о десериализации вынуждает все оценивать с нетерпением в этот момент?

UPDATE

Исходя из принятого ответа 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
}

1 Ответ

1 голос
/ 01 мая 2019

Десериализацию последовательности в Json.NET можно сделать ленивой, но это не так автоматически. Вместо этого вам придется адаптировать один из ответов из Анализ большого файла json в .NET или Newtonsoft JSon Десериализовать в тип примитива к ф #.

Чтобы подтвердить, что десериализация последовательности не является ленивой по умолчанию, определите следующую функцию:

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, вызов JsonSerializer.Deserialize<T>() с someType seq - это то же самое, что вызов с IEnumerable<someType> из c #, и в этом случае Json .NET материализует результат и возвращает его как List<someType>.

Демонстрационная скрипка # 1 здесь .

Чтобы проанализировать массив JSON как ленивую последовательность, вам нужно будет вручную создать функцию seq, которая выполняет итерации по JSON с JsonReader.Read() и десериализует и возвращает каждую запись массива:

let jsonSeqFromStream<'T>(stream : Stream) =
    seq {
        // Adapted from this answer https://stackoverflow.com/a/35298655
        // To https://stackoverflow.com/questions/35295220/newtonsoft-json-deserialize-into-primitive-type
        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
    }

(Поскольку отслеживание, анализируем ли мы значения массива, является состоянием, это не выглядит очень функциональным. Может быть, это можно сделать лучше?)

Возвращение этой функции может быть использовано следующим образом, например ::

let mySeq = jsonSeqFromStream<someType>(stream)

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

Демонстрационная скрипка # 2 здесь .

...