Skip to content

Random-access document model for JSON (JsonDocument) #28132

@bartonjs

Description

@bartonjs

JsonDocument

This class holds the parsed model of the JSON payload. It rents storage from an array pool, and this is IDisposable to return the resources back.

Like XmlDocument, this type only really has a creation routine (Parse) and access to the root element.

An instance takes a ReadOnlyMemory during construction, and holds that ReadOnlyMemory until Dispose (caller modification of the data between Parse and Dispose leads to non-deterministic, unsupported behavior).

JsonElement

This type is effectively a cursor in the document. It is tied to the document that created it, so when that document gets disposed the ObjectDisposedExceptions leak out from this types members. To keep memory utilization at a minimum it is a struct which is effectively a union of what would otherwise be JsonArray, JsonObject, JsonComment, JsonString, JsonNumber, JsonProperty, and whatever we'd do for true/false/null.

Update

Based on the previous API review:

  • JsonDocument.Parse now also accepts string, ReadOnlySpan<char>, and ReadOnlySequence<byte>, and manages things appropriately.
  • (Try)GetRawBytes is gone
  • TryGetValue overloads are now Try-prefixed versions of the non-Try methods (e.g. TryGetInt32())
  • [Try]GetDecimal was added
  • [Try]GetSingle and [Try]GetUInt32 were added
  • The string (and ReadOnlySpan<char> and ReadOnlySpan<byte>) indexer(s) are now a GetProperty method group to convey that they're more than O(1) expensive.
  • Split EnumerateChildren to EnumerateArray and EnumerateObject.
  • Custom enumerable/enumerators now implement the interfaces
  • EnumerateObject gets JsonProperty values instead of JsonElement (name is delay allocated as a System.String)
  • Added JsonValueType, which is JsonTokenType except
    • StartObject => Object
    • StartArray => Array
    • None => Undefined (to match the ECMAScript undefined value)
    • -EndObject
    • -EndArray
    • -Comment
  • JsonReaderOptions inputs to JsonDocument.Parse are defaulted to default

Changes NOT made:

  • No first-class support for nullable at this time
  • The property indexer (now deleted) does not return a nullable JsonElement, because neither the indexer nor methods lifted
namespace System.Text.Json
{
    public sealed partial class JsonDocument : IDisposable
    {
        public JsonElement RootElement { get; }
        public void Dispose();
        public static JsonDocument Parse(ReadOnlySequence<byte> utf8Json, JsonReaderOptions readerOptions = default);
        public static JsonDocument Parse(System.ReadOnlyMemory<byte> utf8Json, JsonReaderOptions readerOptions = default);
        public static JsonDocument Parse(System.ReadOnlyMemory<char> json, JsonReaderOptions readerOptions = default);
        public static JsonDocument Parse(string json, JsonReaderOptions readerOptions = default);
    }
    public readonly partial struct JsonElement
    {
        // InvalidOperationException if Type is not Array
        // IndexOutOfRangeException when appropriate
        public JsonElement this[int index] { get; }

        public JsonValueType Type { get; }

        // InvalidOperationException if Type is not Array
        public JsonElement.ArrayEnumerator EnumerateArray();
        public int GetArrayLength();

        // InvalidOperationException if Type is not Object
        public JsonElement.ObjectEnumerator EnumerateObject();
        public bool TryGetProperty(ReadOnlySpan<byte> utf8PropertyName, out JsonElement value);
        public bool TryGetProperty(ReadOnlySpan<char> propertyName, out JsonElement value);
        public bool TryGetProperty(string propertyName, out JsonElement value);
        // KeyNotFoundException if the property is not found
        public JsonElement GetProperty(ReadOnlySpan<byte> utf8PropertyName);
        public JsonElement GetProperty(ReadOnlySpan<char> propertyName);
        public JsonElement GetProperty(string propertyName);

        // InvalidOperationException if Type is not True or False
        public bool GetBoolean();

        // InvalidOperationException if Type is not Number
        // FormatException if value does not fit
        public decimal GetDecimal();
        public double GetDouble();
        public int GetInt32();
        public long GetInt64();
        public float GetSingle();
        public string GetString();
        [CLSCompliantAttribute(false)]
        public uint GetUInt32();
        [CLSCompliantAttribute(false)]
        public ulong GetUInt64();

        // InvalidOperationException if Type is not Number
        // false if value does not fit.
        public bool TryGetDecimal(out decimal value);
        public bool TryGetDouble(out double value);
        public bool TryGetInt32(out int value);
        public bool TryGetInt64(out long value);
        public bool TryGetSingle(out float value);
        [CLSCompliantAttribute(false)]
        public bool TryGetUInt32(out uint value);
        [CLSCompliantAttribute(false)]
        public bool TryGetUInt64(out ulong value);

        public override string ToString();

        public partial struct ArrayEnumerator : IEnumerable<JsonElement>, IEnumerator<JsonElement>, IEnumerable, IEnumerator, IDisposable
        {
            public JsonElement Current { get; }
            public void Dispose();
            public JsonElement.ArrayEnumerator GetEnumerator();
            public bool MoveNext();
            public void Reset();
        }
        public partial struct ObjectEnumerator : IEnumerable<JsonProperty>, IEnumerator<JsonProperty>, IEnumerable, IEnumerator, IDisposable
        {
            public JsonProperty Current { get; }
            public void Dispose();
            public JsonElement.ObjectEnumerator GetEnumerator();
            public bool MoveNext();
            public void Reset();
        }
    }
    public partial readonly struct JsonProperty
    {
        public string Name { get; }
        public JsonElement Value { get; }
    }
    public enum JsonValueType : byte
    {
        Array = (byte)2,
        False = (byte)6,
        Null = (byte)7,
        Number = (byte)4,
        Object = (byte)1,
        String = (byte)3,
        True = (byte)5,
        Undefined = (byte)0,
    }
    public partial struct Utf8JsonWriter
    {
        public void WriteElement(string propertyName, JsonElement value);
        public void WriteElement(ReadOnlySpan<char> propertyName, JsonElement value);
        public void WriteElement(ReadOnlySpan<byte> propertyName, JsonElement value);
        public void WriteElementValue(JsonElement element);
    }
}

In order to keep the overhead low this type does not support data manipulation (insert/append, delete, or update).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions