Description
Background and motivation
In some performance-intensive applications, there is a need to parse JsonDocuments at a low-level into non-standard data structures. One of those data structures is an F# List. Because it is a singly-linked list, calling JsonElement.EnumerateArray()
requires iterating the elements forward, and then iterating through them backwards to cons
(::
) the list elements back together in the original order. This also requires an unnecessary intermediate allocation of some other (mutable) collection type.
Exposing a reverse-direction enumerator would allow us to parse the JSON array into a linked list without paying an additional iteration or allocation cost.
API Proposal
The API I'm proposing would be exactly the same as the existing ArrayEnumerator
, but it would be called ReverseArrayEnumerator
and its MoveNext()
method would start at the end of the JSON array in the JsonDocument
and move backwards through the elements.
I tried to make sense of the existing ArrayEnumerator struct and reverse-engineer it to make my own, but I got turned around as a layman.
I think implementation would just boil down to changing the _idx
values that get set during the existing MoveNext method.
API Usage
let inline private decodeSeq primitiveErrMsg decoder path node =
if Ast.isArray node then
let mutable i = -1
let mutable isErrored = false
let mutable error : string * ErrorReason = Operators.Unchecked.defaultof<string * ErrorReason>
let mutable results = []
let mutable arrEnumerator = node.EnumerateArray()
while not isErrored && arrEnumerator.MoveNext() do
i <- i + 1
match arrEnumerator.Current |> decoder $"{path}[{i}]" with
| Ok decoded -> results <- decoded :: results // <- this is the important bit. being able to directly `::` the list items together and avoid iteration/allocation overhead
| Error err -> error <- err
if not isErrored then Ok results else Error error
else
Error(path, BadPrimitive(primitiveErrMsg, node))
Alternative Designs
I don't think there is an alternative given the existing API that
- doesn't incur +1 iteration overhead (for linked lists)
- doesn't incur allocation overhead (for linked lists)
- preserves source document order
Risks
This feels like a fairly low-level API change that wouldn't have widespread impact and the behavior should be self-documenting in the name. I'm not aware of any risks (but that doesn't mean there aren't any!).