Skip to content

[API Proposal]: Expose JsonElement.ReverseArrayEnumerator to allow reverse iteration of arrays in JsonDocuments #116320

Closed
@olo-ntaylor

Description

@olo-ntaylor

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!).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions