JsonArray and transformations API implementation (dotnet/corefx#40469)
authorKatarzyna Bułat <t-kabul@microsoft.com>
Mon, 26 Aug 2019 22:51:04 +0000 (15:51 -0700)
committerGitHub <noreply@github.com>
Mon, 26 Aug 2019 22:51:04 +0000 (15:51 -0700)
* JsonArray added
* Transformations API added
* Review Comments included

Commit migrated from https://github.com/dotnet/corefx/commit/ad4fdd9cd3d99d0240fa53b9b7bdc23bb7a3f4a8

26 files changed:
src/libraries/System.Text.Json/docs/writable_json_dom_spec.md
src/libraries/System.Text.Json/ref/System.Text.Json.cs
src/libraries/System.Text.Json/src/Resources/Strings.resx
src/libraries/System.Text.Json/src/System.Text.Json.csproj
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.ArrayEnumerator.cs
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.ObjectEnumerator.cs
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs
src/libraries/System.Text.Json/src/System/Text/Json/Document/JsonProperty.cs
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArray.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArrayEnumerator.cs [new file with mode: 0644]
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonBoolean.cs
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNode.cs
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonNumber.cs
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObject.cs
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonObjectEnumerator.cs
src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonString.cs
src/libraries/System.Text.Json/src/System/Text/Json/ThrowHelper.cs
src/libraries/System.Text.Json/tests/JsonArrayTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/JsonNodeCloneTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/JsonNodeParseTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/JsonNodeTestData.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/JsonNodeToJsonElementTests.cs [new file with mode: 0644]
src/libraries/System.Text.Json/tests/JsonObjectTests.TestData.cs [deleted file]
src/libraries/System.Text.Json/tests/JsonObjectTests.cs
src/libraries/System.Text.Json/tests/JsonStringTests.cs
src/libraries/System.Text.Json/tests/System.Text.Json.Tests.csproj

index 8a3288a..9e033ea 100644 (file)
@@ -243,6 +243,7 @@ Mailbox.SendAllEmployeesData(employees.AsJsonElement());
 * Do we want to add recursive equals on `JsonArray` and `JsonObject`?
 * Do we want to make `JsonNode` derived types implement `IComparable` (which ones)?
 * Do we want `JsonObject` to implement `IDictionary` and `JsonArray` to implement `IList` (currently JsonArray does, but JsonObject not)? 
+* Do we want `JsonArray` to support `Contains`, `IndexOf` and `LastIndexOf` if we keep reference equality for `JsonArray`/`JsonObject` and don't have a good way of comparing numbers? 
 * Would escaped characters be supported for creating `JsonNumber` from string? 
 * Is the API for `JsonNode` and `JsonElement` interactions sufficient? 
 * Do we want to support duplicate and order preservation/control when adding/removing values in `JsonArray`/`JsonObject`?
@@ -251,7 +252,7 @@ Mailbox.SendAllEmployeesData(employees.AsJsonElement());
     | Solution | Pros | Cons |
     |----------|:-------------|--------|
     |current API| - no additional checks need to be made | - creating recursive loop by the user may be problematic |
-    |tracking nodes | - handles recursive loop problem | - when node is added to a parent, it needs to be checked <br>  if it already has a parent  and make a copy if it has |
+    |tracking nodes | - handles recursive loop problem | - when node is added to a parent, it needs to be checked <br>  if it already has a parent and make a copy if it has |
 * Do we want to change `JsonNumber`'s backing field to something different than `string`?     
     Suggestions: 
     - `Span<byte>` or array of `Utf8String`/`Char8` (once they come online in the future) / `byte`  
@@ -262,7 +263,12 @@ Mailbox.SendAllEmployeesData(employees.AsJsonElement());
 * Should `ToString` on `JsonBoolean` and `JsonString` return the .NET or JSON representation?
 * Do we want to keep implicit cast operators (even though for `JsonNumber` it would mean throwing in some cases, which is against FDG)?
 * Do we want overloads that take a `StringComparison` enum for methods retrieving properties? It would make API easier to use where case is not important.
-* Where do we want to enable user to set handling properties manner?
+* Where do we want to enable user to set handling properties manner? Do we want to support it as `JsonElement` does not?
+* Do we want to support transforming `JsonObject` into JSON string? Should `ToString` behave this way or do we want an additional method - e.g. `GetJsonString` (it might be confusing as we have a type `JsonString`) or `GetJsonRepresenation`?
+* `AsJsonElement` function on `JsonNode` currently does not work for `null` node. Do we want to support null case somehow?
+* Do we want both `Clone` and `DeepCopy` methods for `JsonNode`? 
+* Are we OK with needing to cast `JsonArray` to `JsonNode` in order to add it to `JsonObject`? (there's an ambiguous call between `JsonArray` and `IEnumerable<JsonNode>` right now)
+* Do we want `IsImmutable` property for `JsonElement`?
 
 ## Useful links
 
index d39590d..262999f 100644 (file)
@@ -13,11 +13,97 @@ namespace System.Text.Json
         Ignore = 1,
         Error = 2,
     }
+    public sealed partial class JsonArray : System.Text.Json.JsonNode, System.Collections.Generic.ICollection<System.Text.Json.JsonNode>, System.Collections.Generic.IEnumerable<System.Text.Json.JsonNode>, System.Collections.Generic.IList<System.Text.Json.JsonNode>, System.Collections.Generic.IReadOnlyCollection<System.Text.Json.JsonNode>, System.Collections.Generic.IReadOnlyList<System.Text.Json.JsonNode>, System.Collections.IEnumerable
+    {
+        public JsonArray() { }
+        public JsonArray(System.Collections.Generic.IEnumerable<bool> values) { }
+        public JsonArray(System.Collections.Generic.IEnumerable<byte> values) { }
+        public JsonArray(System.Collections.Generic.IEnumerable<decimal> values) { }
+        public JsonArray(System.Collections.Generic.IEnumerable<double> values) { }
+        public JsonArray(System.Collections.Generic.IEnumerable<short> values) { }
+        public JsonArray(System.Collections.Generic.IEnumerable<int> values) { }
+        public JsonArray(System.Collections.Generic.IEnumerable<long> values) { }
+        [System.CLSCompliantAttribute(false)]
+        public JsonArray(System.Collections.Generic.IEnumerable<sbyte> values) { }
+        public JsonArray(System.Collections.Generic.IEnumerable<float> values) { }
+        public JsonArray(System.Collections.Generic.IEnumerable<string> values) { }
+        public JsonArray(System.Collections.Generic.IEnumerable<System.Text.Json.JsonNode> values) { }
+        [System.CLSCompliantAttribute(false)]
+        public JsonArray(System.Collections.Generic.IEnumerable<ushort> values) { }
+        [System.CLSCompliantAttribute(false)]
+        public JsonArray(System.Collections.Generic.IEnumerable<uint> values) { }
+        [System.CLSCompliantAttribute(false)]
+        public JsonArray(System.Collections.Generic.IEnumerable<ulong> values) { }
+        public int Count { get { throw null; } }
+        public bool IsReadOnly { get { throw null; } }
+        public System.Text.Json.JsonNode this[int idx] { get { throw null; } set { } }
+        public override System.Text.Json.JsonValueKind ValueKind { get { throw null; } }
+        public void Add(bool value) { }
+        public void Add(byte value) { }
+        public void Add(decimal value) { }
+        public void Add(double value) { }
+        public void Add(short value) { }
+        public void Add(int value) { }
+        public void Add(long value) { }
+        [System.CLSCompliantAttribute(false)]
+        public void Add(sbyte value) { }
+        public void Add(float value) { }
+        public void Add(string value) { }
+        public void Add(System.Text.Json.JsonNode value) { }
+        [System.CLSCompliantAttribute(false)]
+        public void Add(ushort value) { }
+        [System.CLSCompliantAttribute(false)]
+        public void Add(uint value) { }
+        [System.CLSCompliantAttribute(false)]
+        public void Add(ulong value) { }
+        public void Clear() { }
+        public override System.Text.Json.JsonNode Clone() { throw null; }
+        public bool Contains(System.Text.Json.JsonNode value) { throw null; }
+        public System.Text.Json.JsonArrayEnumerator GetEnumerator() { throw null; }
+        public int IndexOf(System.Text.Json.JsonNode item) { throw null; }
+        public void Insert(int index, bool item) { }
+        public void Insert(int index, byte item) { }
+        public void Insert(int index, decimal item) { }
+        public void Insert(int index, double item) { }
+        public void Insert(int index, short item) { }
+        public void Insert(int index, int item) { }
+        public void Insert(int index, long item) { }
+        [System.CLSCompliantAttribute(false)]
+        public void Insert(int index, sbyte item) { }
+        public void Insert(int index, float item) { }
+        public void Insert(int index, string item) { }
+        public void Insert(int index, System.Text.Json.JsonNode item) { }
+        [System.CLSCompliantAttribute(false)]
+        public void Insert(int index, ushort item) { }
+        [System.CLSCompliantAttribute(false)]
+        public void Insert(int index, uint item) { }
+        [System.CLSCompliantAttribute(false)]
+        public void Insert(int index, ulong item) { }
+        public int LastIndexOf(System.Text.Json.JsonNode item) { throw null; }
+        public bool Remove(System.Text.Json.JsonNode item) { throw null; }
+        public int RemoveAll(System.Predicate<System.Text.Json.JsonNode> match) { throw null; }
+        public void RemoveAt(int index) { }
+        void System.Collections.Generic.ICollection<System.Text.Json.JsonNode>.CopyTo(System.Text.Json.JsonNode[] array, int arrayIndex) { }
+        System.Collections.Generic.IEnumerator<System.Text.Json.JsonNode> System.Collections.Generic.IEnumerable<System.Text.Json.JsonNode>.GetEnumerator() { throw null; }
+        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
+    }
+    public partial struct JsonArrayEnumerator : System.Collections.Generic.IEnumerator<System.Text.Json.JsonNode>, System.Collections.IEnumerator, System.IDisposable
+    {
+        private object _dummy;
+        public JsonArrayEnumerator(System.Text.Json.JsonArray jsonArray) { throw null; }
+        public System.Text.Json.JsonNode Current { get { throw null; } }
+        object System.Collections.IEnumerator.Current { get { throw null; } }
+        public void Dispose() { }
+        public bool MoveNext() { throw null; }
+        void System.Collections.IEnumerator.Reset() { }
+    }
     public sealed partial class JsonBoolean : System.Text.Json.JsonNode, System.IEquatable<System.Text.Json.JsonBoolean>
     {
         public JsonBoolean() { }
         public JsonBoolean(bool value) { }
         public bool Value { get { throw null; } set { } }
+        public override System.Text.Json.JsonValueKind ValueKind { get { throw null; } }
+        public override System.Text.Json.JsonNode Clone() { throw null; }
         public override bool Equals(object obj) { throw null; }
         public bool Equals(System.Text.Json.JsonBoolean other) { throw null; }
         public override int GetHashCode() { throw null; }
@@ -58,6 +144,7 @@ namespace System.Text.Json
     {
         private readonly object _dummy;
         private readonly int _dummyPrimitive;
+        public bool IsImmutable { get { throw null; } }
         public System.Text.Json.JsonElement this[int index] { get { throw null; } }
         public System.Text.Json.JsonValueKind ValueKind { get { throw null; } }
         public System.Text.Json.JsonElement Clone() { throw null; }
@@ -178,6 +265,13 @@ namespace System.Text.Json
     public abstract partial class JsonNode
     {
         internal JsonNode() { }
+        public abstract System.Text.Json.JsonValueKind ValueKind { get; }
+        public System.Text.Json.JsonElement AsJsonElement() { throw null; }
+        public abstract System.Text.Json.JsonNode Clone();
+        public static System.Text.Json.JsonNode DeepCopy(System.Text.Json.JsonElement jsonElement) { throw null; }
+        public static System.Text.Json.JsonNode GetNode(System.Text.Json.JsonElement jsonElement) { throw null; }
+        public static System.Text.Json.JsonNode Parse(string json) { throw null; }
+        public static bool TryGetNode(System.Text.Json.JsonElement jsonElement, out System.Text.Json.JsonNode jsonNode) { throw null; }
     }
     public sealed partial class JsonNumber : System.Text.Json.JsonNode, System.IEquatable<System.Text.Json.JsonNumber>
     {
@@ -198,6 +292,8 @@ namespace System.Text.Json
         public JsonNumber(uint value) { }
         [System.CLSCompliantAttribute(false)]
         public JsonNumber(ulong value) { }
+        public override System.Text.Json.JsonValueKind ValueKind { get { throw null; } }
+        public override System.Text.Json.JsonNode Clone() { throw null; }
         public override bool Equals(object obj) { throw null; }
         public bool Equals(System.Text.Json.JsonNumber other) { throw null; }
         public byte GetByte() { throw null; }
@@ -273,9 +369,11 @@ namespace System.Text.Json
         public System.Text.Json.JsonNode this[string propertyName] { get { throw null; } set { } }
         public System.Collections.Generic.ICollection<string> PropertyNames { get { throw null; } }
         public System.Collections.Generic.ICollection<System.Text.Json.JsonNode> PropertyValues { get { throw null; } }
+        public override System.Text.Json.JsonValueKind ValueKind { get { throw null; } }
         public void Add(System.Collections.Generic.KeyValuePair<string, System.Text.Json.JsonNode> jsonProperty) { }
         public void Add(string propertyName, bool propertyValue) { }
         public void Add(string propertyName, byte propertyValue) { }
+        public void Add(string propertyName, System.Collections.Generic.IEnumerable<System.Text.Json.JsonNode> propertyValues) { }
         public void Add(string propertyName, System.DateTime propertyValue) { }
         public void Add(string propertyName, System.DateTimeOffset propertyValue) { }
         public void Add(string propertyName, decimal propertyValue) { }
@@ -297,12 +395,16 @@ namespace System.Text.Json
         [System.CLSCompliantAttribute(false)]
         public void Add(string propertyName, ulong propertyValue) { }
         public void AddRange(System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string, System.Text.Json.JsonNode>> jsonProperties) { }
+        public override System.Text.Json.JsonNode Clone() { throw null; }
         public bool ContainsProperty(string propertyName) { throw null; }
-        public System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, System.Text.Json.JsonNode>> GetEnumerator() { throw null; }
+        public System.Text.Json.JsonObjectEnumerator GetEnumerator() { throw null; }
+        public System.Text.Json.JsonArray GetJsonArrayPropertyValue(string propertyName) { throw null; }
         public System.Text.Json.JsonObject GetJsonObjectPropertyValue(string propertyName) { throw null; }
         public System.Text.Json.JsonNode GetPropertyValue(string propertyName) { throw null; }
         public bool Remove(string propertyName) { throw null; }
+        System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<string, System.Text.Json.JsonNode>> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String,System.Text.Json.JsonNode>>.GetEnumerator() { throw null; }
         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; }
+        public bool TryGetJsonArrayPropertyValue(string propertyName, out System.Text.Json.JsonArray jsonArray) { throw null; }
         public bool TryGetJsonObjectPropertyValue(string propertyName, out System.Text.Json.JsonObject jsonObject) { throw null; }
         public bool TryGetPropertyValue(string propertyName, out System.Text.Json.JsonNode jsonNode) { throw null; }
     }
@@ -314,7 +416,7 @@ namespace System.Text.Json
         object System.Collections.IEnumerator.Current { get { throw null; } }
         public void Dispose() { }
         public bool MoveNext() { throw null; }
-        public void Reset() { }
+        void System.Collections.IEnumerator.Reset() { }
     }
     public readonly partial struct JsonProperty
     {
@@ -386,13 +488,21 @@ namespace System.Text.Json
         public JsonString(System.ReadOnlySpan<char> value) { }
         public JsonString(string value) { }
         public string Value { get { throw null; } set { } }
+        public override System.Text.Json.JsonValueKind ValueKind { get { throw null; } }
+        public override System.Text.Json.JsonNode Clone() { throw null; }
         public override bool Equals(object obj) { throw null; }
         public bool Equals(System.Text.Json.JsonString other) { throw null; }
+        public System.DateTime GetDateTime() { throw null; }
+        public System.DateTimeOffset GetDateTimeOffset() { throw null; }
+        public System.Guid GetGuid() { throw null; }
         public override int GetHashCode() { throw null; }
         public static bool operator ==(System.Text.Json.JsonString left, System.Text.Json.JsonString right) { throw null; }
         public static implicit operator System.Text.Json.JsonString (string value) { throw null; }
         public static bool operator !=(System.Text.Json.JsonString left, System.Text.Json.JsonString right) { throw null; }
         public override string ToString() { throw null; }
+        public bool TryGetDateTime(out System.DateTime value) { throw null; }
+        public bool TryGetDateTimeOffset(out System.DateTimeOffset value) { throw null; }
+        public bool TryGetGuid(out System.Guid value) { throw null; }
     }
     public enum JsonTokenType : byte
     {
index 0b044d0..8892115 100644 (file)
   <data name="InvalidDuplicatePropertyNameHandling" xml:space="preserve">
     <value>The DuplicatePropertyNameHandling enum must be set to one of the supported values.</value>
   </data>
+  <data name="ArrayModifiedDuringIteration" xml:space="preserve">
+    <value>The JSON array was modified during iteration.</value>
+  </data>
 </root>
\ No newline at end of file
index bbbae86..3e2278e 100644 (file)
   </ItemGroup>
   <ItemGroup>
     <Compile Include="System\Text\Json\Node\DuplicatePropertyNameHandling.cs" />
+    <Compile Include="System\Text\Json\Node\JsonArray.cs" />
+    <Compile Include="System\Text\Json\Node\JsonArrayEnumerator.cs" />
     <Compile Include="System\Text\Json\Node\JsonBoolean.cs" />
     <Compile Include="System\Text\Json\Node\JsonNode.cs" />
     <Compile Include="System\Text\Json\Node\JsonNumber.cs" />
index 2362bdd..34edd8c 100644 (file)
@@ -18,20 +18,51 @@ namespace System.Text.Json
         {
             private readonly JsonElement _target;
             private int _curIdx;
-            private readonly int _endIdx;
+            private readonly int _endIdxOrVersion;
 
             internal ArrayEnumerator(JsonElement target)
             {
-                Debug.Assert(target.TokenType == JsonTokenType.StartArray);
-
                 _target = target;
                 _curIdx = -1;
-                _endIdx = _target._parent.GetEndIndex(_target._idx, includeEndElement: false);
+
+                if (target._parent is JsonDocument document)
+                {
+                    Debug.Assert(target.TokenType == JsonTokenType.StartArray);
+
+                    _endIdxOrVersion = document.GetEndIndex(_target._idx, includeEndElement: false);
+                }
+                else
+                {
+                    var jsonArray = (JsonArray)target._parent;
+
+                    _endIdxOrVersion = jsonArray._version;
+                }
             }
 
             /// <inheritdoc />
-            public JsonElement Current =>
-                _curIdx < 0 ? default : new JsonElement(_target._parent, _curIdx);
+            public JsonElement Current
+            {
+                get
+                {
+                    if (_curIdx < 0)
+                    {
+                        return default;
+                    }
+
+                    if (_target._parent is JsonArray jsonArray)
+                    {
+                        if (_curIdx >= jsonArray.Count)
+                        {
+                            return default;
+                        }
+
+                        return jsonArray[_curIdx].AsJsonElement();
+                    }
+
+                    var document = (JsonDocument)_target._parent;
+                    return new JsonElement(document, _curIdx);
+                }
+            }
 
             /// <summary>
             ///   Returns an enumerator that iterates through a collection.
@@ -56,7 +87,7 @@ namespace System.Text.Json
             /// <inheritdoc />
             public void Dispose()
             {
-                _curIdx = _endIdx;
+                _curIdx = _endIdxOrVersion;
             }
 
             /// <inheritdoc />
@@ -71,7 +102,23 @@ namespace System.Text.Json
             /// <inheritdoc />
             public bool MoveNext()
             {
-                if (_curIdx >= _endIdx)
+                if (_target._parent is JsonArray jsonArray)
+                {
+                    if (jsonArray._version != _endIdxOrVersion)
+                    {
+                        throw new InvalidOperationException(SR.ArrayModifiedDuringIteration);
+                    }
+
+                    if (_curIdx >= jsonArray.Count)
+                    {
+                        return false;
+                    }
+
+                    _curIdx++;
+                    return _curIdx < jsonArray.Count;
+                }
+
+                if (_curIdx >= _endIdxOrVersion)
                 {
                     return false;
                 }
@@ -82,10 +129,11 @@ namespace System.Text.Json
                 }
                 else
                 {
-                    _curIdx = _target._parent.GetEndIndex(_curIdx, includeEndElement: true);
+                    var document = (JsonDocument)_target._parent;
+                    _curIdx = document.GetEndIndex(_curIdx, includeEndElement: true);
                 }
 
-                return _curIdx < _endIdx;
+                return _curIdx < _endIdxOrVersion;
             }
         }
     }
index 2b2b40a..92968ba 100644 (file)
@@ -19,21 +19,62 @@ namespace System.Text.Json
             private readonly JsonElement _target;
             private int _curIdx;
             private readonly int _endIdx;
+            private JsonObjectEnumerator _jsonObjectEnumerator;
 
             internal ObjectEnumerator(JsonElement target)
             {
-                Debug.Assert(target.TokenType == JsonTokenType.StartObject);
-
                 _target = target;
                 _curIdx = -1;
-                _endIdx = _target._parent.GetEndIndex(_target._idx, includeEndElement: false);
+
+                if (target._parent is JsonDocument document)
+                {
+                    Debug.Assert(target.TokenType == JsonTokenType.StartObject);
+
+                    _endIdx = document.GetEndIndex(_target._idx, includeEndElement: false);
+                    _jsonObjectEnumerator = default;
+                }
+                else
+                {
+                    _endIdx = -1;
+
+                    var jsonObject = (JsonObject)target._parent;
+                    _jsonObjectEnumerator = new JsonObjectEnumerator(jsonObject);
+                }
             }
 
             /// <inheritdoc />
-            public JsonProperty Current =>
-                _curIdx < 0 ?
-                    default :
-                    new JsonProperty(new JsonElement(_target._parent, _curIdx));
+            public JsonProperty Current
+            {
+                get
+                {
+                    if (!_target.IsImmutable)
+                    {
+                        KeyValuePair<string, JsonNode> propertyPair = _jsonObjectEnumerator.Current;
+
+                        // propertyPair.Key is null before first after last call of MoveNext
+                        if (propertyPair.Key == null)
+                        {
+                            return default;
+                        }
+
+                        // null JsonNode case
+                        if (propertyPair.Value == null)
+                        {
+                            return new JsonProperty(new JsonElement(null), propertyPair.Key);
+                        }
+
+                        return new JsonProperty(propertyPair.Value.AsJsonElement(), propertyPair.Key);
+                    }
+
+                    if (_curIdx < 0)
+                    {
+                        return default;
+                    }
+
+                    var document = (JsonDocument)_target._parent;
+                    return new JsonProperty(new JsonElement(document, _curIdx));
+                }
+            }
 
             /// <summary>
             ///   Returns an enumerator that iterates the properties of an object.
@@ -65,12 +106,20 @@ namespace System.Text.Json
             public void Dispose()
             {
                 _curIdx = _endIdx;
+                if (!_target.IsImmutable)
+                {
+                    _jsonObjectEnumerator.Dispose();
+                }
             }
 
             /// <inheritdoc />
             public void Reset()
             {
                 _curIdx = -1;
+                if (!_target.IsImmutable)
+                {
+                    ((IEnumerator)_jsonObjectEnumerator).Reset();
+                }
             }
 
             /// <inheritdoc />
@@ -79,6 +128,11 @@ namespace System.Text.Json
             /// <inheritdoc />
             public bool MoveNext()
             {
+                if (!_target.IsImmutable)
+                {
+                    return _jsonObjectEnumerator.MoveNext();
+                }
+
                 if (_curIdx >= _endIdx)
                 {
                     return false;
@@ -90,7 +144,8 @@ namespace System.Text.Json
                 }
                 else
                 {
-                    _curIdx = _target._parent.GetEndIndex(_curIdx, includeEndElement: true);
+                    var document = (JsonDocument)_target._parent;
+                    _curIdx = document.GetEndIndex(_curIdx, includeEndElement: true);
                 }
 
                 // _curIdx is now pointing at a property name, move one more to get the value
index 9fa492d..94bf579 100644 (file)
@@ -13,7 +13,7 @@ namespace System.Text.Json
     [DebuggerDisplay("{DebuggerDisplay,nq}")]
     public readonly partial struct JsonElement
     {
-        private readonly JsonDocument _parent;
+        internal readonly object _parent;
         private readonly int _idx;
 
         internal JsonElement(JsonDocument parent, int idx)
@@ -27,15 +27,51 @@ namespace System.Text.Json
             _idx = idx;
         }
 
-        private JsonTokenType TokenType => _parent?.GetJsonTokenType(_idx) ?? JsonTokenType.None;
+        internal JsonElement(JsonNode parent)
+        {
+            _parent = parent;
+            _idx = -1;
+        }
+
+        /// <summary>
+        ///   Indicates whether or not this instance is immutable.
+        /// </summary>
+        public bool IsImmutable => _idx != -1;
 
+        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+        private JsonTokenType TokenType
+        {
+            get
+            {
+                JsonDocument document = (JsonDocument)_parent;
+                return document?.GetJsonTokenType(_idx) ?? JsonTokenType.None;
+            }
+        }
         /// <summary>
         ///   The <see cref="JsonValueKind"/> that the value is.
         /// </summary>
         /// <exception cref="ObjectDisposedException">
         ///   The parent <see cref="JsonDocument"/> has been disposed.
         /// </exception>
-        public JsonValueKind ValueKind => TokenType.ToValueKind();
+        public JsonValueKind ValueKind
+        {
+            get
+            {
+                if (IsImmutable)
+                {
+                    return TokenType.ToValueKind();
+                }
+
+                var jsonNode = (JsonNode)_parent;
+
+                if (jsonNode == null)
+                {
+                    return JsonValueKind.Null;
+                }
+
+                return jsonNode.ValueKind;
+            }
+        }
 
         /// <summary>
         ///   Get the value at a specified index when the current value is a
@@ -56,7 +92,19 @@ namespace System.Text.Json
             {
                 CheckValidInstance();
 
-                return _parent.GetArrayIndexElement(_idx, index);
+                if (_parent is JsonDocument document)
+                {
+                    return document.GetArrayIndexElement(_idx, index);
+                }
+
+                var jsonNode = (JsonNode)_parent;
+
+                if (jsonNode is JsonArray jsonArray)
+                {
+                    return jsonArray[index].AsJsonElement();
+                }
+
+                throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Array, jsonNode.ValueKind);
             }
         }
 
@@ -74,7 +122,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.GetArrayLength(_idx);
+            if (_parent is JsonDocument document)
+            {
+                return document.GetArrayLength(_idx);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonArray jsonArray)
+            {
+                return jsonArray.Count;
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Array, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -264,7 +324,26 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetNamedPropertyValue(_idx, propertyName, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetNamedPropertyValue(_idx, propertyName, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonObject jsonObject)
+            {
+                if (jsonObject.TryGetPropertyValue(propertyName.ToString(), out JsonNode nodeValue))
+                {
+                    value = nodeValue.AsJsonElement();
+                    return true;
+                }
+
+                value = default;
+                return false;
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Object, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -300,7 +379,26 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetNamedPropertyValue(_idx, utf8PropertyName, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetNamedPropertyValue(_idx, utf8PropertyName, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonObject jsonObject)
+            {
+                if (jsonObject.TryGetPropertyValue(JsonHelpers.Utf8GetString(utf8PropertyName), out JsonNode nodeValue))
+                {
+                    value = nodeValue.AsJsonElement();
+                    return true;
+                }
+
+                value = default;
+                return false;
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Object, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -319,15 +417,26 @@ namespace System.Text.Json
         /// </exception>
         public bool GetBoolean()
         {
-            // CheckValidInstance is redundant.  Asking for the type will
-            // return None, which then throws the same exception in the return statement.
+            CheckValidInstance();
+
+            if (_parent is JsonDocument document)
+            {
+                JsonTokenType type = TokenType;
 
-            JsonTokenType type = TokenType;
+                return
+                    type == JsonTokenType.True ? true :
+                    type == JsonTokenType.False ? false :
+                    throw ThrowHelper.GetJsonElementWrongTypeException(nameof(Boolean), type);
+            }
+
+            var jsonNode = (JsonNode)_parent;
 
-            return
-                type == JsonTokenType.True ? true :
-                type == JsonTokenType.False ? false :
-                throw ThrowHelper.GetJsonElementWrongTypeException(nameof(Boolean), type);
+            if (_parent is JsonBoolean jsonBoolean)
+            {
+                return jsonBoolean.Value;
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(nameof(Boolean), jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -348,7 +457,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.GetString(_idx, JsonTokenType.String);
+            if (_parent is JsonDocument document)
+            {
+                return document.GetString(_idx, JsonTokenType.String);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonString jsonString)
+            {
+                return jsonString.Value;
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.String, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -356,7 +477,7 @@ namespace System.Text.Json
         /// </summary>
         /// <param name="value">Receives the value.</param>
         /// <remarks>
-        ///  This method does not create a byte[] representation of values other than bsae 64 encoded JSON strings.
+        ///  This method does not create a byte[] representation of values other than base 64 encoded JSON strings.
         /// </remarks>
         /// <returns>
         ///   <see langword="true"/> if the entire token value is encoded as valid Base64 text and can be successfully decoded to bytes.
@@ -372,7 +493,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonString)
+            {
+                throw new NotSupportedException();
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.String, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -424,7 +557,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonNumber jsonNumber)
+            {
+                return jsonNumber.TryGetSByte(out value);
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Number, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -472,7 +617,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonNumber jsonNumber)
+            {
+                return jsonNumber.TryGetByte(out value);
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Number, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -522,7 +679,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (_parent is JsonNumber jsonNumber)
+            {
+                return jsonNumber.TryGetInt16(out value);
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Number, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -570,7 +739,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (_parent is JsonNumber jsonNumber)
+            {
+                return jsonNumber.TryGetUInt16(out value);
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Number, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -621,7 +802,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonNumber jsonNumber)
+            {
+                return jsonNumber.TryGetInt32(out value);
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Number, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -669,7 +862,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonNumber jsonNumber)
+            {
+                return jsonNumber.TryGetUInt32(out value);
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Number, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -720,7 +925,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonNumber jsonNumber)
+            {
+                return jsonNumber.TryGetInt64(out value);
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Number, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -771,7 +988,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonNumber jsonNumber)
+            {
+                return jsonNumber.TryGetUInt64(out value);
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Number, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -831,7 +1060,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (_parent is JsonNumber jsonNumber)
+            {
+                return jsonNumber.TryGetDouble(out value);
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Number, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -898,7 +1139,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonNumber jsonNumber)
+            {
+                return jsonNumber.TryGetSingle(out value);
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Number, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -957,7 +1210,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonNumber jsonNumber)
+            {
+                return jsonNumber.TryGetDecimal(out value);
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Number, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -1008,7 +1273,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonString jsonString)
+            {
+                return jsonString.TryGetDateTime(out value);
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.String, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -1059,7 +1336,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonString jsonString)
+            {
+                return jsonString.TryGetDateTimeOffset(out value);
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.String, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -1110,7 +1399,19 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TryGetValue(_idx, out value);
+            if (_parent is JsonDocument document)
+            {
+                return document.TryGetValue(_idx, out value);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+
+            if (jsonNode is JsonString jsonString)
+            {
+                return jsonString.TryGetGuid(out value);
+            }
+
+            throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.String, jsonNode.ValueKind);
         }
 
         /// <summary>
@@ -1144,7 +1445,8 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.GetNameOfPropertyValue(_idx);
+            var document = (JsonDocument)_parent;
+            return document.GetNameOfPropertyValue(_idx);
         }
 
         /// <summary>
@@ -1160,14 +1462,21 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.GetRawValueAsString(_idx);
+            if (_parent is JsonDocument document)
+            {
+                return document.GetRawValueAsString(_idx);
+            }
+
+            var jsonNode = (JsonNode)_parent;
+            return jsonNode.ToString();
         }
 
         internal string GetPropertyRawText()
         {
             CheckValidInstance();
 
-            return _parent.GetPropertyRawValueAsString(_idx);
+            var document = (JsonDocument)_parent;
+            return document.GetPropertyRawValueAsString(_idx);
         }
 
         /// <summary>
@@ -1258,14 +1567,16 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            return _parent.TextEquals(_idx, utf8Text, isPropertyName);
+            var document = (JsonDocument)_parent;
+            return document.TextEquals(_idx, utf8Text, isPropertyName);
         }
 
         internal bool TextEqualsHelper(ReadOnlySpan<char> text, bool isPropertyName)
         {
             CheckValidInstance();
 
-            return _parent.TextEquals(_idx, text, isPropertyName);
+            var document = (JsonDocument)_parent;
+            return document.TextEquals(_idx, text, isPropertyName);
         }
 
         /// <summary>
@@ -1290,7 +1601,8 @@ namespace System.Text.Json
 
             CheckValidInstance();
 
-            _parent.WriteElementTo(_idx, writer);
+            var document = (JsonDocument)_parent;
+            document.WriteElementTo(_idx, writer);
         }
 
         /// <summary>
@@ -1309,11 +1621,21 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            JsonTokenType tokenType = TokenType;
+            if (_parent is JsonDocument)
+            {
+                JsonTokenType tokenType = TokenType;
 
-            if (tokenType != JsonTokenType.StartArray)
+                if (tokenType != JsonTokenType.StartArray)
+                {
+                    throw ThrowHelper.GetJsonElementWrongTypeException(JsonTokenType.StartArray, tokenType);
+                }
+            }
+            else if (_parent is JsonNode node)
             {
-                throw ThrowHelper.GetJsonElementWrongTypeException(JsonTokenType.StartArray, tokenType);
+                if (node.ValueKind != JsonValueKind.Array)
+                {
+                    throw new InvalidOperationException();
+                }
             }
 
             return new ArrayEnumerator(this);
@@ -1335,11 +1657,21 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            JsonTokenType tokenType = TokenType;
+            if (_parent is JsonDocument)
+            {
+                JsonTokenType tokenType = TokenType;
 
-            if (tokenType != JsonTokenType.StartObject)
+                if (tokenType != JsonTokenType.StartObject)
+                {
+                    throw ThrowHelper.GetJsonElementWrongTypeException(JsonTokenType.StartObject, tokenType);
+                }
+            }
+            else if (_parent is JsonNode node)
             {
-                throw ThrowHelper.GetJsonElementWrongTypeException(JsonTokenType.StartObject, tokenType);
+                if (node.ValueKind != JsonValueKind.Object)
+                {
+                    throw new InvalidOperationException();
+                }
             }
 
             return new ObjectEnumerator(this);
@@ -1392,7 +1724,13 @@ namespace System.Text.Json
                     {
                         // null parent should have hit the None case
                         Debug.Assert(_parent != null);
-                        return _parent.GetRawValueAsString(_idx);
+                        if (_parent is JsonDocument document)
+                        {
+                            return document.GetRawValueAsString(_idx);
+                        }
+
+                        var jsonNode = (JsonNode)_parent;
+                        return jsonNode.ToString();
                     }
                 case JsonTokenType.String:
                     return GetString();
@@ -1422,12 +1760,18 @@ namespace System.Text.Json
         {
             CheckValidInstance();
 
-            if (!_parent.IsDisposable)
+            if (_parent is JsonDocument document)
             {
-                return this;
+                if (!document.IsDisposable)
+                {
+                    return this;
+                }
+
+                return document.CloneElement(_idx);
             }
 
-            return _parent.CloneElement(_idx);
+            var jsonNode = (JsonNode)_parent;
+            return jsonNode.Clone().AsJsonElement();
         }
 
         private void CheckValidInstance()
@@ -1436,6 +1780,8 @@ namespace System.Text.Json
             {
                 throw new InvalidOperationException();
             }
+
+            Debug.Assert(_parent is JsonDocument || _parent is JsonNode);
         }
 
         [DebuggerBrowsable(DebuggerBrowsableState.Never)]
index 79d8fd8..ac6265d 100644 (file)
@@ -17,16 +17,18 @@ namespace System.Text.Json
         ///   The value of this property.
         /// </summary>
         public JsonElement Value { get; }
+        private string _name { get; }
 
-        internal JsonProperty(JsonElement value)
+        internal JsonProperty(JsonElement value, string name = null)
         {
             Value = value;
+            _name = name;
         }
 
         /// <summary>
         ///   The name of this property.
         /// </summary>
-        public string Name => Value.GetPropertyName();
+        public string Name => _name ?? Value.GetPropertyName();
 
         /// <summary>
         ///   Compares <paramref name="text" /> to the name of this property.
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArray.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArray.cs
new file mode 100644 (file)
index 0000000..13b7b7a
--- /dev/null
@@ -0,0 +1,583 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Collections.Generic;
+
+namespace System.Text.Json
+{
+    /// <summary>
+    ///   Represents a JSON array.
+    /// </summary>
+    public sealed class JsonArray : JsonNode, IList<JsonNode>, IReadOnlyList<JsonNode>
+    {
+        internal readonly List<JsonNode> _list;
+        internal int _version;
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the empty array.
+        /// </summary>
+        public JsonArray()
+        {
+            _list = new List<JsonNode>();
+            _version = 0;
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the specified collection.
+        /// </summary>
+        /// <param name="values">Collection to represent.</param>
+        public JsonArray(IEnumerable<JsonNode> values)
+        {
+            _list = new List<JsonNode>(values);
+            _version = 0;
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the specified collection of <see cref="string"/>s.
+        /// </summary>
+        /// <param name="values">Collection to represent.</param>
+        /// <exception cref="ArgumentNullException">
+        ///   Some of provided values are null.
+        /// </exception>
+        public JsonArray(IEnumerable<string> values) : this()
+        {
+            foreach (string value in values)
+            {
+                if (value == null)
+                {
+                    _list.Add(null);
+                }
+
+                _list.Add(new JsonString(value));
+            }
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the specified collection of <see cref="byte"/>s.
+        /// </summary>
+        /// <param name="values">Collection to represent.</param>
+        public JsonArray(IEnumerable<bool> values) : this()
+        {
+            foreach (bool value in values)
+            {
+                _list.Add(new JsonBoolean(value));
+            }
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the specified collection of <see cref="byte"/>s.
+        /// </summary>
+        /// <param name="values">Collection to represent.</param>
+        public JsonArray(IEnumerable<byte> values) : this()
+        {
+            foreach (byte value in values)
+            {
+                _list.Add(new JsonNumber(value));
+            }
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the specified collection of <see cref="short"/>s.
+        /// </summary>
+        /// <param name="values">Collection to represent.</param>
+        public JsonArray(IEnumerable<short> values) : this()
+        {
+            foreach (short value in values)
+            {
+                _list.Add(new JsonNumber(value));
+            }
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the specified collection of <see cref="int"/>s.
+        /// </summary>
+        /// <param name="values">Collection to represent.</param>
+        public JsonArray(IEnumerable<int> values) : this()
+        {
+            foreach (int value in values)
+            {
+                _list.Add(new JsonNumber(value));
+            }
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the specified collection of <see cref="long"/>s.
+        /// </summary>
+        /// <param name="values">Collection to represent.</param>
+        public JsonArray(IEnumerable<long> values) : this()
+        {
+            foreach (long value in values)
+            {
+                _list.Add(new JsonNumber(value));
+            }
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the specified collection of <see cref="float"/>s.
+        /// </summary>
+        /// <param name="values">Collection to represent.</param>
+        /// <exception cref="ArgumentException">
+        ///   Some of provided values are not in a legal JSON number format.
+        /// </exception>
+        public JsonArray(IEnumerable<float> values) : this()
+        {
+            foreach (float value in values)
+            {
+                _list.Add(new JsonNumber(value));
+            }
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the specified collection of <see cref="double"/>s.
+        /// </summary>
+        /// <param name="values">Collection to represent.</param>
+        /// <exception cref="ArgumentException">
+        ///   Some of provided values are not in a legal JSON number format.
+        /// </exception>
+        public JsonArray(IEnumerable<double> values) : this()
+        {
+            foreach (double value in values)
+            {
+                _list.Add(new JsonNumber(value));
+            }
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the specified collection of <see cref="sbyte"/>s.
+        /// </summary>
+        /// <param name="values">Collection to represent.</param>
+        [CLSCompliant(false)]
+        public JsonArray(IEnumerable<sbyte> values) : this()
+        {
+            foreach (sbyte value in values)
+            {
+                _list.Add(new JsonNumber(value));
+            }
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the specified collection of <see cref="ushort"/>s.
+        /// </summary>
+        /// <param name="values">Collection to represent.</param>
+        [CLSCompliant(false)]
+        public JsonArray(IEnumerable<ushort> values) : this()
+        {
+            foreach (ushort value in values)
+            {
+                _list.Add(new JsonNumber(value));
+            }
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the specified collection of <see cref="uint"/>s.
+        /// </summary>
+        /// <param name="values">Collection to represent.</param>
+        [CLSCompliant(false)]
+        public JsonArray(IEnumerable<uint> values) : this()
+        {
+            foreach (uint value in values)
+            {
+                _list.Add(new JsonNumber(value));
+            }
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the specified collection of <see cref="ulong"/>s.
+        /// </summary>
+        /// <param name="values">Collection to represent.</param>
+        [CLSCompliant(false)]
+        public JsonArray(IEnumerable<ulong> values) : this()
+        {
+            foreach (ulong value in values)
+            {
+                _list.Add(new JsonNumber(value));
+            }
+        }
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArray"/> class representing the specified collection of <see cref="decimal"/>s.
+        /// </summary>
+        /// <param name="values">Collection to represent.</param>
+        public JsonArray(IEnumerable<decimal> values) : this()
+        {
+            foreach (decimal value in values)
+            {
+                _list.Add(new JsonNumber(value));
+            }
+        }
+
+        /// <summary>
+        ///   Gets or sets the element at the specified index.
+        /// </summary>
+        /// <param name="idx">The zero-based index of the element to get or set.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   Index is less than 0.
+        /// </exception>
+        public JsonNode this[int idx]
+        {
+            get => _list[idx];
+            set
+            {
+                _list[idx] = value;
+                _version++;
+            }
+        }
+
+        /// <summary>
+        ///   Adds the specified <see cref="JsonNode"/> value as the last item in this collection.
+        /// </summary>
+        /// <param name="value">The value to add.</param>
+        /// <remarks>Null value is allowed and represents the JSON null value.</remarks>
+        public void Add(JsonNode value)
+        {
+            _list.Add(value);
+            _version++;
+        }
+
+        /// <summary>
+        ///   Adds the specified value as a <see cref="JsonString"/> as the last item in this collection.
+        /// </summary>
+        /// <param name="value">The value to add.</param>
+        /// <remarks>Null value is allowed and represents the JSON null value.</remarks>
+        public void Add(string value)
+        {
+            if (value == null)
+            {
+                Add((JsonNode)null);
+            }
+            else
+            {
+                Add(new JsonString(value));
+            }
+        }
+
+        /// <summary>
+        ///   Adds the specified value as a <see cref="JsonBoolean"/> as the last item in this collection.
+        /// </summary>
+        /// <param name="value">The value to add.</param>
+        public void Add(bool value) => Add(new JsonBoolean(value));
+
+        /// <summary>
+        ///   Adds the specified value as a <see cref="JsonNumber"/> as the last item in this collection.
+        /// </summary>
+        /// <param name="value">The value to add.</param>
+        public void Add(byte value) => Add(new JsonNumber(value));
+
+        /// <summary>
+        ///   Adds the specified value as a <see cref="JsonNumber"/> as the last item in this collection.
+        /// </summary>
+        /// <param name="value">The value to add.</param>
+        public void Add(short value) => Add(new JsonNumber(value));
+
+        /// <summary>
+        ///   Adds the specified value as a <see cref="JsonNumber"/> as the last item in this collection.
+        /// </summary>
+        /// <param name="value">The value to add.</param>
+        public void Add(int value) => Add(new JsonNumber(value));
+
+        /// <summary>
+        ///   Adds the specified value as a <see cref="JsonNumber"/> as the last item in this collection.
+        /// </summary>
+        /// <param name="value">The value to add.</param>
+        public void Add(long value) => Add(new JsonNumber(value));
+
+        /// <summary>
+        ///   Adds the specified value as a <see cref="JsonNumber"/> as the last item in this collection.
+        /// </summary>
+        /// <param name="value">The value to add.</param>
+        /// <exception cref="ArgumentException">
+        ///   Provided value is not in a legal JSON number format.
+        /// </exception>
+        public void Add(float value) => Add(new JsonNumber(value));
+
+        /// <summary>
+        ///   Adds the specified value as a <see cref="JsonNumber"/> as the last item in this collection.
+        /// </summary>
+        /// <param name="value">The value to add.</param>
+        /// <exception cref="ArgumentException">
+        ///   Provided value is not in a legal JSON number format.
+        /// </exception>
+        public void Add(double value) => Add(new JsonNumber(value));
+
+        /// <summary>
+        ///   Adds the specified value as a <see cref="JsonNumber"/> as the last item in this collection.
+        /// </summary>
+        /// <param name="value">The value to add.</param>
+        [CLSCompliant(false)]
+        public void Add(sbyte value) => Add(new JsonNumber(value));
+
+        /// <summary>
+        ///   Adds the specified value as a <see cref="JsonNumber"/> as the last item in this collection.
+        /// </summary>
+        /// <param name="value">The value to add.</param>
+        [CLSCompliant(false)]
+        public void Add(ushort value) => Add(new JsonNumber(value));
+
+        /// <summary>
+        ///   Adds the specified value as a <see cref="JsonNumber"/> as the last item in this collection.
+        /// </summary>
+        /// <param name="value">The value to add.</param>
+        [CLSCompliant(false)]
+        public void Add(uint value) => Add(new JsonNumber(value));
+
+        /// <summary>
+        ///   Adds the specified value as a <see cref="JsonNumber"/> as the last item in this collection.
+        /// </summary>
+        /// <param name="value">The value to add.</param>
+        [CLSCompliant(false)]
+        public void Add(ulong value) => Add(new JsonNumber(value));
+
+        /// <summary>
+        ///   Adds the specified value as a <see cref="JsonNumber"/> as the last item in this collection.
+        /// </summary>
+        /// <param name="value">The value to add.</param>
+        public void Add(decimal value) => Add(new JsonNumber(value));
+
+        /// <summary>
+        ///   Inserts the specified item at the specified index of the JSON array.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The item to add.</param>
+        /// <remarks>The <paramref name="item"/> parameter may be <see langword="null" />, which represents the JSON null value.</remarks>
+        public void Insert(int index, JsonNode item)
+        {
+            _list.Insert(index, item);
+            _version++;
+        }
+
+        /// <summary>
+        ///   Inserts the specified item as a <see cref="JsonString"/> at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The item to add.</param>
+        public void Insert(int index, string item) => Insert(index, new JsonString(item));
+
+        /// <summary>
+        ///   Inserts the specified item as a <see cref="JsonBoolean"/> at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The item to add.</param>
+        public void Insert(int index, bool item) => Insert(index, new JsonBoolean(item));
+
+        /// <summary>
+        ///   Inserts the specified item as a <see cref="JsonNumber"/> at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The item to add.</param>
+        public void Insert(int index, byte item) => Insert(index, new JsonNumber(item));
+
+        /// <summary>
+        ///   Inserts the specified item as a <see cref="JsonNumber"/> at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The item to add.</param>
+        public void Insert(int index, short item) => Insert(index, new JsonNumber(item));
+
+        /// <summary>
+        ///   Inserts the specified item as a <see cref="JsonNumber"/> at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The item to add.</param>
+        public void Insert(int index, int item) => Insert(index, new JsonNumber(item));
+
+        /// <summary>
+        ///   Inserts the specified item as a <see cref="JsonNumber"/> at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The item to add.</param>
+        public void Insert(int index, long item) => Insert(index, new JsonNumber(item));
+
+        /// <summary>
+        ///   Inserts the specified item as a <see cref="JsonNumber"/> at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The item to add.</param>
+        /// <exception cref="ArgumentException">
+        ///   Provided value is not in a legal JSON number format.
+        /// </exception>
+        public void Insert(int index, float item) => Insert(index, new JsonNumber(item));
+
+        /// <summary>
+        ///   Inserts the specified item as a <see cref="JsonNumber"/> at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The item to add.</param>
+        /// <exception cref="ArgumentException">
+        ///   Provided value is not in a legal JSON number format.
+        /// </exception>
+        public void Insert(int index, double item) => Insert(index, new JsonNumber(item));
+
+        /// <summary>
+        ///   Inserts the specified item as a <see cref="JsonNumber"/> at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The item to add.</param>
+        [CLSCompliant(false)]
+        public void Insert(int index, sbyte item) => Insert(index, new JsonNumber(item));
+
+        /// <summary>
+        ///   Inserts the specified item as a <see cref="JsonNumber"/> at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The item to add.</param>
+        [CLSCompliant(false)]
+        public void Insert(int index, ushort item) => Insert(index, new JsonNumber(item));
+
+        /// <summary>
+        ///   Inserts the specified item as a <see cref="JsonNumber"/> at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The item to add.</param>
+        [CLSCompliant(false)]
+        public void Insert(int index, uint item) => Insert(index, new JsonNumber(item));
+
+        /// <summary>
+        ///   Inserts the specified item as a <see cref="JsonNumber"/> at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The item to add.</param>
+        [CLSCompliant(false)]
+        public void Insert(int index, ulong item) => Insert(index, new JsonNumber(item));
+
+        /// <summary>
+        ///   Inserts the specified item as a <see cref="JsonNumber"/> at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
+        /// <param name="item">The item to add.</param>
+        public void Insert(int index, decimal item) => Insert(index, new JsonNumber(item));
+
+        /// <summary>
+        ///   Determines whether a specified <see cref="JsonNode"/> element is in a collection.
+        /// </summary>
+        /// <param name="value">Value to check.</param>
+        /// <returns>
+        ///   <see langword="true"/> if the value is successfully found in a collection,
+        ///   <see langword="false"/> otherwise.
+        /// </returns>
+        public bool Contains(JsonNode value) => _list.Contains(value);
+
+        /// <summary>
+        ///   Gets the number of elements contained in the collection.
+        /// </summary>
+        public int Count => _list.Count;
+
+        /// <summary>
+        ///   Gets a value indicating whether the collection is read-only.
+        /// </summary>
+        public bool IsReadOnly => false;
+
+        /// <summary>
+        ///   Returns the zero-based index of the first occurrence of a specified item in the collection.
+        /// </summary>
+        /// <param name="item">Item to find.</param>
+        /// <returns>The zero-based starting index of the search. 0 (zero) is valid in an empty collection.</returns>
+        public int IndexOf(JsonNode item) => _list.IndexOf(item);
+
+        /// <summary>
+        ///   Returns the zero-based index of the last occurrence of a specified item in the collection.
+        /// </summary>
+        /// <param name="item">Item to find.</param>
+        /// <returns>The zero-based starting index of the search. 0 (zero) is valid in an empty collection.</returns>
+        public int LastIndexOf(JsonNode item) => _list.LastIndexOf(item);
+
+        /// <summary>
+        ///   Removes all elements from the JSON array.
+        /// </summary>
+        public void Clear()
+        {
+            _list.Clear();
+            _version++;
+        }
+
+        /// <summary>
+        ///   Removes the first occurrence of a specific object from the collection.
+        /// </summary>
+        /// <param name="item">
+        ///   The object to remove from the collection. The value can be null and it represents null collection.
+        /// </param>
+        /// <returns>
+        ///   <see langword="true"/> if the item is successfully found in a collection and removed,
+        ///   <see langword="false"/> otherwise.
+        /// </returns>
+        public bool Remove(JsonNode item)
+        {
+            _version++;
+            return _list.Remove(item);
+        }
+
+        /// <summary>
+        ///   Removes all the elements that match the conditions defined by the specified predicate.
+        /// </summary>
+        /// <param name="match">
+        ///   Thepredicate delegate that defines the conditions of the elements to remove.
+        /// </param>
+        /// <returns>The number of elements removed from the collection.</returns>
+        public int RemoveAll(Predicate<JsonNode> match)
+        {
+            _version++;
+            return _list.RemoveAll(match);
+        }
+        /// <summary>
+        ///   Removes the item at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">
+        ///   The zero-based index of the element to remove.
+        /// </param>
+        public void RemoveAt(int index)
+        {
+            _list.RemoveAt(index);
+            _version++;
+        }
+
+        /// <summary>
+        ///   Copies the collection or a portion of it to an array.
+        /// </summary>
+        /// <param name="array">
+        ///   The one-dimensional array that is the destination of the elements copied from collection.
+        ///   The array must have zero-based indexing.
+        /// </param>
+        /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param>
+        void ICollection<JsonNode>.CopyTo(JsonNode[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
+
+        /// <summary>
+        ///   Returns an enumerator that iterates through the collection values.
+        /// </summary>
+        /// <returns>An enumerator structure for the <see cref="JsonArray"/>.</returns>
+        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+        /// <summary>
+        ///   Returns an enumerator that iterates through the collection values.
+        /// </summary>
+        /// <returns>An enumerator structure for the <see cref="JsonArray"/>.</returns>
+        IEnumerator<JsonNode> IEnumerable<JsonNode>.GetEnumerator() => new JsonArrayEnumerator(this);
+
+        /// <summary>
+        ///   Returns an enumerator that iterates through the collection values.
+        /// </summary>
+        /// <returns>An enumerator structure for the <see cref="JsonArray"/>.</returns>
+        public JsonArrayEnumerator GetEnumerator() => new JsonArrayEnumerator(this);
+
+        /// <summary>
+        ///   Creates a new collection that is a copy of the current instance.
+        /// </summary>
+        /// <returns>A new collection that is a copy of this instance.</returns>
+        public override JsonNode Clone()
+        {
+            var jsonArray = new JsonArray();
+
+            foreach (JsonNode jsonNode in _list)
+            {
+                jsonArray.Add(jsonNode.Clone());
+            }
+
+            return jsonArray;
+        }
+
+        /// <summary>
+        ///   Returns <see cref="JsonValueKind.Array"/>
+        /// </summary>
+        public override JsonValueKind ValueKind { get => JsonValueKind.Array;}
+    }
+}
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArrayEnumerator.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Node/JsonArrayEnumerator.cs
new file mode 100644 (file)
index 0000000..1dc16a2
--- /dev/null
@@ -0,0 +1,45 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace System.Text.Json
+{
+    /// <summary>
+    ///   Supports an iteration over a JSON array.
+    /// </summary>
+    public struct JsonArrayEnumerator : IEnumerator<JsonNode>
+    {
+        private List<JsonNode>.Enumerator _enumerator;
+
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="JsonArrayEnumerator"/> class supporting an interation over provided JSON array.
+        /// </summary>
+        /// <param name="jsonArray">JSON array to iterate over.</param>
+        public JsonArrayEnumerator(JsonArray jsonArray) => _enumerator = jsonArray._list.GetEnumerator();
+
+        /// <summary>
+        ///   Gets the property in the JSON array at the current position of the enumerator.
+        /// </summary>
+        public JsonNode Current => _enumerator.Current;
+
+        /// <summary>
+        ///    Gets the property in the JSON array at the current position of the enumerator.
+        /// </summary>
+        object IEnumerator.Current => _enumerator.Current;
+
+        /// <summary>
+        ///   Releases all resources used by the <see cref="JsonObjectEnumerator"/>.
+        /// </summary>
+        public void Dispose() => _enumerator.Dispose();
+
+        /// <summary>
+        ///   Advances the enumerator to the next property of the JSON array.
+        /// </summary>
+        /// <returns></returns>
+        public bool MoveNext() => _enumerator.MoveNext();
+
+        /// <summary>
+        ///   Sets the enumerator to its initial position, which is before the first element in the JSON array.
+        /// </summary>
+        void IEnumerator.Reset() => ((IEnumerator)_enumerator).Reset();
+    }
+}
index 985ed48..7a358dd 100644 (file)
@@ -94,5 +94,16 @@ namespace System.Text.Json
         ///   <see langword="false"/> otherwise.
         /// </returns>
         public static bool operator !=(JsonBoolean left, JsonBoolean right) => !(left == right);
+
+        /// <summary>
+        ///   Creates a new JSON boolean that is a copy of the current instance.
+        /// </summary>
+        /// <returns>A new JSON boolean that is a copy of this instance.</returns>
+        public override JsonNode Clone() => new JsonBoolean(Value);
+
+        /// <summary>
+        ///   Returns <see cref="JsonValueKind.True"/> or <see cref="JsonValueKind.False"/>, accordingly to the represented value.
+        /// </summary>
+        public override JsonValueKind ValueKind { get => Value ? JsonValueKind.True : JsonValueKind.False; }
     }
 }
index ec16a66..c075840 100644 (file)
@@ -2,6 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Diagnostics;
+
 namespace System.Text.Json
 {
     /// <summary>
@@ -10,5 +12,112 @@ namespace System.Text.Json
     public abstract class JsonNode
     {
         private protected JsonNode() { }
+
+        /// <summary>
+        ///   Transforms this instance into <see cref="JsonElement"/> representation.
+        ///   Operations performed on this instance will modify the returned <see cref="JsonElement"/>.
+        /// </summary>
+        /// <returns>Mutable <see cref="JsonElement"/> with <see cref="JsonNode"/> underneath.</returns>
+        public JsonElement AsJsonElement() => new JsonElement(this);
+
+        /// <summary>
+        ///   The <see cref="JsonValueKind"/> that the node is.
+        /// </summary>
+        public abstract JsonValueKind ValueKind { get; }
+
+        /// <summary>
+        ///   Gets the <see cref="JsonNode"/> represented by <paramref name="jsonElement"/>.
+        ///   Operations performed on the returned <see cref="JsonNode"/> will modify the <paramref name="jsonElement"/>.
+        /// </summary>
+        /// <param name="jsonElement"><see cref="JsonElement"/> to get the <see cref="JsonNode"/> from.</param>
+        /// <returns><see cref="JsonNode"/> represented by <paramref name="jsonElement"/>.</returns>
+        public static JsonNode GetNode(JsonElement jsonElement) => (JsonNode)jsonElement._parent;
+
+        /// <summary>
+        ///    Gets the <see cref="JsonNode"/> represented by the <paramref name="jsonElement"/>.
+        ///    Operations performed on the returned <see cref="JsonNode"/> will modify the <paramref name="jsonElement"/>.
+        ///    A return value indicates whether the operation succeeded.
+        /// </summary>
+        /// <param name="jsonElement"><see cref="JsonElement"/> to get the <see cref="JsonNode"/> from.</param>
+        /// <param name="jsonNode"><see cref="JsonNode"/> represented by <paramref name="jsonElement"/>.</param>
+        /// <returns>
+        ///  <see langword="true"/> if the operation succeded;
+        ///  otherwise, <see langword="false"/>
+        /// </returns>
+        public static bool TryGetNode(JsonElement jsonElement, out JsonNode jsonNode)
+        {
+            if (!jsonElement.IsImmutable)
+            {
+                jsonNode = (JsonNode)jsonElement._parent;
+                return true;
+            }
+
+            jsonNode = null;
+            return false;
+        }
+
+        /// <summary>
+        ///   Parses a string representiong JSON document into <see cref="JsonNode"/>.
+        /// </summary>
+        /// <param name="json">JSON to parse.</param>
+        /// <returns><see cref="JsonNode"/> representation of <paramref name="json"/>.</returns>
+        public static JsonNode Parse(string json)
+        {
+            using (JsonDocument document = JsonDocument.Parse(json))
+            {
+                return DeepCopy(document.RootElement);
+            }
+        }
+
+        /// <summary>
+        ///   Performs a deep copy operation on this instance.
+        /// </summary>
+        /// <returns><see cref="JsonNode"/> which is a clone of this instance.</returns>
+        public abstract JsonNode Clone();
+
+        /// <summary>
+        ///   Performs a deep copy operation on <paramref name="jsonElement"/> returning corresponding modifiable tree structure of JSON nodes.
+        ///   Operations performed on returned <see cref="JsonNode"/> does not modify <paramref name="jsonElement"/>.
+        /// </summary>
+        /// <param name="jsonElement"><see cref="JsonElement"/> to copy.</param>
+        /// <returns><see cref="JsonNode"/>  representing <paramref name="jsonElement"/>.</returns>
+        public static JsonNode DeepCopy(JsonElement jsonElement)
+        {
+            if (!jsonElement.IsImmutable)
+            {
+                return GetNode(jsonElement).Clone();
+            }
+
+            switch (jsonElement.ValueKind)
+            {
+                case JsonValueKind.Object:
+                    JsonObject jsonObject = new JsonObject();
+                    foreach (JsonProperty property in jsonElement.EnumerateObject())
+                    {
+                        jsonObject.Add(property.Name, DeepCopy(property.Value));
+                    }
+                    return jsonObject;
+                case JsonValueKind.Array:
+                    JsonArray jsonArray = new JsonArray();
+                    foreach (JsonElement element in jsonElement.EnumerateArray())
+                    {
+                        jsonArray.Add(DeepCopy(element));
+                    }
+                    return jsonArray;
+                case JsonValueKind.Number:
+                    return new JsonNumber(jsonElement.GetRawText());
+                case JsonValueKind.String:
+                    return new JsonString(jsonElement.GetString());
+                case JsonValueKind.True:
+                    return new JsonBoolean(true);
+                case JsonValueKind.False:
+                    return new JsonBoolean(false);
+                case JsonValueKind.Null:
+                    return null;
+                default:
+                    Debug.Assert(jsonElement.ValueKind == JsonValueKind.Undefined, "No handler for JsonValueKind.{jsonElement.ValueKind}");
+                    throw ThrowHelper.GetJsonElementWrongTypeException(JsonValueKind.Undefined, jsonElement.ValueKind);
+            }
+        }
     }
 }
index a4c639d..041131c 100644 (file)
@@ -684,5 +684,16 @@ namespace System.Text.Json
         ///   <see langword="false"/> otherwise.
         /// </returns>
         public static bool operator !=(JsonNumber left, JsonNumber right) => !(left == right);
+
+        /// <summary>
+        ///   Creates a new JSON number that is a copy of the current instance.
+        /// </summary>
+        /// <returns>A new JSON number that is a copy of this instance.</returns>
+        public override JsonNode Clone() => new JsonNumber(_value);
+
+        /// <summary>
+        ///   Returns <see cref="JsonValueKind.Number"/>
+        /// </summary>
+        public override JsonValueKind ValueKind { get => JsonValueKind.Number; }
     }
 }
index 19a35a1..90e456b 100644 (file)
@@ -68,15 +68,6 @@ namespace System.Text.Json
         }
 
         /// <summary>
-        ///   Returns an enumerator that iterates through the JSON object properties.
-        /// </summary>
-        /// <returns>An enumerator structure for the JSON object.</returns>
-        /// <exception cref="ArgumentException">
-        ///   Property name to set already exists if handling duplicates is set to <see cref="DuplicatePropertyNameHandling.Error"/>.
-        /// </exception>
-        public IEnumerator<KeyValuePair<string, JsonNode>> GetEnumerator() => new JsonObjectEnumerator(this);
-
-        /// <summary>
         ///   Adds the specified property to the JSON object.
         /// </summary>
         /// <param name="jsonProperty">The property to add.</param>
@@ -139,7 +130,7 @@ namespace System.Text.Json
         ///   Adds the specified property as a <see cref="JsonString"/> to the JSON object.
         /// </summary>
         /// <param name="propertyName">Name of the property to add.</param>
-        /// <param name="propertyValue"><see cref="ReadOnlySpan{T}"/> value of the property to add.</param>
+        /// <param name="propertyValue"><see cref="ReadOnlySpan{Char}"/> value of the property to add.</param>
         /// <exception cref="ArgumentException">
         ///   Property name to set already exists if handling duplicates is set to <see cref="DuplicatePropertyNameHandling.Error"/>.
         /// </exception>
@@ -372,6 +363,27 @@ namespace System.Text.Json
         }
 
         /// <summary>
+        ///   Adds the property values from the specified collection as a <see cref="JsonArray"/> property to the JSON object.
+        /// </summary>
+        /// <param name="propertyName">Name of the <see cref="JsonArray"/> property to add.</param>
+        /// <param name="propertyValues">Properties to add.</param>
+        /// <exception cref="ArgumentException">
+        ///   Provided collection contains duplicates if handling duplicates is set to <see cref="DuplicatePropertyNameHandling.Error"/>.
+        /// </exception>
+        /// <exception cref="ArgumentNullException">
+        ///   Some of property names are null.
+        /// </exception>
+        public void Add(string propertyName, IEnumerable<JsonNode> propertyValues)
+        {
+            var jsonArray = new JsonArray();
+            foreach (JsonNode value in propertyValues)
+            {
+                jsonArray.Add(value);
+            }
+            Add(propertyName, (JsonNode)jsonArray);
+        }
+
+        /// <summary>
         ///   Removes the property with the specified name.
         /// </summary>
         /// <param name="propertyName"></param>
@@ -438,7 +450,7 @@ namespace System.Text.Json
         /// <exception cref="KeyNotFoundException">
         ///   Property with specified name is not found in JSON object.
         /// </exception>
-        /// <exception cref="InvalidCastException">
+        /// <exception cref="ArgumentException">
         ///   Property with specified name is not a JSON object.
         /// </exception>
         public JsonObject GetJsonObjectPropertyValue(string propertyName)
@@ -448,7 +460,7 @@ namespace System.Text.Json
                 return jsonObject;
             }
 
-            throw new InvalidCastException(SR.Format(SR.PropertyTypeMismatch, propertyName));
+            throw new ArgumentException(SR.Format(SR.PropertyTypeMismatch, propertyName));
         }
 
         /// <summary>
@@ -464,11 +476,8 @@ namespace System.Text.Json
         {
             if (TryGetPropertyValue(propertyName, out JsonNode jsonNode))
             {
-                if (jsonNode is JsonObject jsonNodeCasted)
-                {
-                    jsonObject = jsonNodeCasted;
-                    return true;
-                }
+                jsonObject = jsonNode as JsonObject;
+                return jsonObject != null;
             }
 
             jsonObject = null;
@@ -476,6 +485,48 @@ namespace System.Text.Json
         }
 
         /// <summary>
+        ///   Returns the JSON array value of a property with the specified name.
+        /// </summary>
+        /// <param name="propertyName">Name of the property to return.</param>
+        /// <returns>JSON objectvalue of a property with the specified name.</returns>
+        /// <exception cref="KeyNotFoundException">
+        ///   Property with specified name is not found in JSON array.
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   Property with specified name is not a JSON array.
+        /// </exception>
+        public JsonArray GetJsonArrayPropertyValue(string propertyName)
+        {
+            if (GetPropertyValue(propertyName) is JsonArray jsonArray)
+            {
+                return jsonArray;
+            }
+
+            throw new ArgumentException(SR.Format(SR.PropertyTypeMismatch, propertyName));
+        }
+
+        /// <summary>
+        ///   Returns the JSON array value of a property with the specified name.
+        /// </summary>
+        /// <param name="propertyName">Name of the property to return.</param>
+        /// <param name="jsonArray">JSON array value of the property with specified name.</param>
+        /// <returns>
+        ///  <see langword="true"/> if JSON array property with specified name was found;
+        ///  otherwise, <see langword="false"/>
+        /// </returns>
+        public bool TryGetJsonArrayPropertyValue(string propertyName, out JsonArray jsonArray)
+        {
+            if (TryGetPropertyValue(propertyName, out JsonNode jsonNode))
+            {
+                jsonArray = jsonNode as JsonArray;
+                return jsonArray != null;
+            }
+
+            jsonArray = null;
+            return false;
+        }
+
+        /// <summary>
         ///   A collection containing the property names of JSON object.
         /// </summary>
         public ICollection<string> PropertyNames => _dictionary.Keys;
@@ -489,6 +540,48 @@ namespace System.Text.Json
         ///   Returns an enumerator that iterates through the JSON object properties.
         /// </summary>
         /// <returns>An enumerator structure for the <see cref="JsonObject"/>.</returns>
-        IEnumerator IEnumerable.GetEnumerator() => new JsonObjectEnumerator(this);
+        /// <exception cref="ArgumentException">
+        ///   Property name to set already exists if handling duplicates is set to <see cref="DuplicatePropertyNameHandling.Error"/>.
+        /// </exception>
+        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+        /// <summary>
+        ///   Returns an enumerator that iterates through the JSON object properties.
+        /// </summary>
+        /// <returns>An enumerator structure for the JSON object.</returns>
+        /// <exception cref="ArgumentException">
+        ///   Property name to set already exists if handling duplicates is set to <see cref="DuplicatePropertyNameHandling.Error"/>.
+        /// </exception>
+        IEnumerator<KeyValuePair<string, JsonNode>> IEnumerable<KeyValuePair<string, JsonNode>>.GetEnumerator() => new JsonObjectEnumerator(this);
+
+        /// <summary>
+        ///   Returns an enumerator that iterates through the JSON object properties.
+        /// </summary>
+        /// <returns>An enumerator structure for the JSON object.</returns>
+        /// <exception cref="ArgumentException">
+        ///   Property name to set already exists if handling duplicates is set to <see cref="DuplicatePropertyNameHandling.Error"/>.
+        /// </exception>
+        public JsonObjectEnumerator GetEnumerator() => new JsonObjectEnumerator(this);
+
+        /// <summary>
+        ///   Creates a new JSON object that is a copy of the current instance.
+        /// </summary>
+        /// <returns>A new JSON object that is a copy of this instance.</returns>
+        public override JsonNode Clone()
+        {
+            var jsonObject = new JsonObject(_duplicatePropertyNameHandling);
+
+            foreach (KeyValuePair<string, JsonNode> property in _dictionary)
+            {
+                jsonObject.Add(property.Key, property.Value.Clone());
+            }
+
+            return jsonObject;
+        }
+
+        /// <summary>
+        ///   Returns <see cref="JsonValueKind.Object"/>
+        /// </summary>
+        public override JsonValueKind ValueKind { get => JsonValueKind.Object; }
     }
 }
index 2723e21..9b7a7b7 100644 (file)
@@ -8,7 +8,7 @@ namespace System.Text.Json
     /// </summary>
     public struct JsonObjectEnumerator : IEnumerator<KeyValuePair<string, JsonNode>>
     {
-        private readonly IEnumerator<KeyValuePair<string, JsonNode>> _enumerator;
+        private Dictionary<string, JsonNode>.Enumerator _enumerator;
 
         /// <summary>
         ///   Initializes a new instance of the <see cref="JsonObjectEnumerator"/> class supporting an interation over provided JSON object.
@@ -40,6 +40,6 @@ namespace System.Text.Json
         /// <summary>
         ///   Sets the enumerator to its initial position, which is before the first element in the JSON object.
         /// </summary>
-        public void Reset() => _enumerator.Reset();
+        void IEnumerator.Reset() => ((IEnumerator)_enumerator).Reset();
     }
 }
index bcf459c..de5538f 100644 (file)
@@ -2,6 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Globalization;
+
 namespace System.Text.Json
 {
     /// <summary>
@@ -35,19 +37,25 @@ namespace System.Text.Json
         ///  Initializes a new instance of the <see cref="JsonString"/> with a string representation of the <see cref="Guid"/> structure.
         /// </summary>
         /// <param name="value">The value to represent as a JSON string.</param>
-        public JsonString(Guid value) => Value = value.ToString();
+        public JsonString(Guid value) => Value = value.ToString("D");
 
         /// <summary>
-        ///  Initializes a new instance of the <see cref="JsonString"/> with a string representation of the <see cref="DateTime"/> structure.
+        ///  Initializes a new instance of the <see cref="JsonString"/> with an ISO 8601 representation of the <see cref="DateTime"/> structure.
         /// </summary>
         /// <param name="value">The value to represent as a JSON string.</param>
-        public JsonString(DateTime value) => Value = value.ToString();
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   The date and time is outside the range of dates supported by the calendar used by the invariant culture.
+        /// </exception>
+        public JsonString(DateTime value) => Value = value.ToString("s", CultureInfo.InvariantCulture);
 
         /// <summary>
-        ///  Initializes a new instance of the <see cref="JsonString"/> with a string representation of the <see cref="DateTimeOffset"/> structure.
+        ///  Initializes a new instance of the <see cref="JsonString"/> with an ISO 8601 representation of the <see cref="DateTimeOffset"/> structure.
         /// </summary>
         /// <param name="value">The value to represent as a JSON string.</param>
-        public JsonString(DateTimeOffset value) => Value = value.ToString();
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   The date and time is outside the range of dates supported by the calendar used by the invariant culture.
+        /// </exception>
+        public JsonString(DateTimeOffset value) => Value = value.ToString("s", CultureInfo.InvariantCulture);
 
         /// <summary>
         ///   Gets or sets the text value represented by the instance.
@@ -68,6 +76,70 @@ namespace System.Text.Json
         public override string ToString() => _value;
 
         /// <summary>
+        ///   Converts the ISO 8601 text value of this instance to its <see cref="DateTime"/> equivalent.
+        /// </summary>
+        /// <returns>A <see cref="DateTime"/> equivalent to the text stored by this instance.</returns>
+        /// <exception cref="FormatException">
+        ///   Text value of this instance is not in an ISO 8601 defined DateTime format.
+        /// </exception>
+        public DateTime GetDateTime() => DateTime.ParseExact(_value, "s", CultureInfo.InvariantCulture);
+
+        /// <summary>
+        ///   Converts the ISO 8601 text value of this instance to <see cref="DateTimeOffset"/> equivalent.
+        /// </summary>
+        /// <returns>A <see cref="DateTimeOffset"/> equivalent to the text stored by this instance.</returns>
+        /// <exception cref="FormatException">
+        ///   Text value of this instance is not in an ISO 8601 defined DateTimeOffset format.
+        /// </exception>
+        public DateTimeOffset GetDateTimeOffset() => DateTimeOffset.ParseExact(_value, "s", CultureInfo.InvariantCulture);
+
+        /// <summary>
+        ///   Converts the text value of this instance to its <see cref="Guid"/> equivalent.
+        /// </summary>
+        /// <returns>A <see cref="Guid"/> equivalent to the text stored by this instance.</returns>
+        /// <exception cref="FormatException">
+        ///   Text value of this instance is not in a GUID recognized format.
+        /// </exception>
+        public Guid GetGuid() => Guid.ParseExact(_value, "D");
+
+        /// <summary>
+        ///   Converts the ISO 8601 text value of this instance to its ISO 8601 <see cref="DateTime"/> equivalent.
+        ///   A return value indicates whether the conversion succeeded.
+        /// </summary>
+        /// <param name="value">
+        ///   When this method returns, contains the see cref="DateTime"/> value equivalent of the text contained in this instance,
+        ///   if the conversion succeeded, or zero if the conversion failed.
+        /// </param>
+        /// <returns>
+        ///  <see langword="true"/> if instance was converted successfully;
+        ///  otherwise, <see langword="false"/>
+        /// </returns>
+        public bool TryGetDateTime(out DateTime value) => DateTime.TryParseExact(_value, "s", CultureInfo.InvariantCulture, DateTimeStyles.None, out value);
+
+        /// <summary>
+        ///   Converts the ISO 8601 text value of this instance to its <see cref="DateTimeOffset"/> equivalent.
+        ///   A return value indicates whether the conversion succeeded.
+        /// </summary>
+        /// <param name="value">
+        ///   When this method returns, contains the <see cref="DateTimeOffset"/> value equivalent of the text contained in this instance,
+        ///   if the conversion succeeded, or zero if the conversion failed.
+        /// </param>
+        /// <returns>
+        ///  <see langword="true"/> if instance was converted successfully;
+        ///  otherwise, <see langword="false"/>
+        /// </returns>
+        public bool TryGetDateTimeOffset(out DateTimeOffset value) => DateTimeOffset.TryParseExact(_value, "s", CultureInfo.InvariantCulture, DateTimeStyles.None, out value);
+
+        /// <summary>
+        ///   Converts the text value of this instance to its <see cref="Guid"/> equivalent.
+        /// </summary>
+        /// <returns>A <see cref="Guid"/> equivalent to the text stored by this instance.</returns>
+        /// <exception cref="FormatException">
+        ///   Text value of this instance is not in a GUID recognized format.
+        /// </exception>
+        public bool TryGetGuid(out Guid value) => Guid.TryParseExact(_value, "D", out value);
+
+        /// <summary>
         ///   Converts a <see cref="string"/> to a <see cref="JsonString"/>.
         /// </summary>
         /// <param name="value">The value to convert.</param>
@@ -131,5 +203,16 @@ namespace System.Text.Json
         ///   <see langword="false"/> otherwise.
         /// </returns>
         public static bool operator !=(JsonString left, JsonString right) => !(left == right);
+
+        /// <summary>
+        ///   Creates a new JSON string that is a copy of the current instance.
+        /// </summary>
+        /// <returns>A new JSON string that is a copy of this instance.</returns>
+        public override JsonNode Clone() => new JsonString(Value);
+
+        /// <summary>
+        ///   Returns <see cref="JsonValueKind.String"/>
+        /// </summary>
+        public override JsonValueKind ValueKind { get => JsonValueKind.String; }
     }
 }
index cbab677..00a9f47 100644 (file)
@@ -231,8 +231,7 @@ namespace System.Text.Json
             JsonTokenType expectedType,
             JsonTokenType actualType)
         {
-            return GetInvalidOperationException(
-                SR.Format(SR.JsonElementHasWrongType, expectedType.ToValueKind(), actualType.ToValueKind()));
+            return GetJsonElementWrongTypeException(expectedType.ToValueKind(), actualType.ToValueKind());
         }
 
         [MethodImpl(MethodImplOptions.NoInlining)]
@@ -240,8 +239,25 @@ namespace System.Text.Json
             string expectedTypeName,
             JsonTokenType actualType)
         {
+            return GetJsonElementWrongTypeException(expectedTypeName, actualType.ToValueKind());
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        internal static InvalidOperationException GetJsonElementWrongTypeException(
+            JsonValueKind expectedType,
+            JsonValueKind actualType)
+        {
+            return GetInvalidOperationException(
+                SR.Format(SR.JsonElementHasWrongType, expectedType, actualType));
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        internal static InvalidOperationException GetJsonElementWrongTypeException(
+            string expectedTypeName,
+            JsonValueKind actualType)
+        {
             return GetInvalidOperationException(
-                SR.Format(SR.JsonElementHasWrongType, expectedTypeName, actualType.ToValueKind()));
+                SR.Format(SR.JsonElementHasWrongType, expectedTypeName, actualType));
         }
 
         public static void ThrowJsonReaderException(ref Utf8JsonReader json, ExceptionResource resource, byte nextByte = default, ReadOnlySpan<byte> bytes = default)
diff --git a/src/libraries/System.Text.Json/tests/JsonArrayTests.cs b/src/libraries/System.Text.Json/tests/JsonArrayTests.cs
new file mode 100644 (file)
index 0000000..89ff9b6
--- /dev/null
@@ -0,0 +1,366 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace System.Text.Json.Tests
+{
+    public static class JsonArrayTests
+    {
+
+        private static void TestArray<T>(T value1, T value2, Func<JsonArray, T> getter, Func<T, JsonNode> nodeCtor)
+        {
+            var value1Casted = value1 as dynamic;
+            var value2Casted = value2 as dynamic;
+
+            JsonNode value1Node = nodeCtor(value1);
+            JsonNode value2Node = nodeCtor(value2);
+
+            var list = new List<T>() { value1 };
+            JsonArray jsonArray = new JsonArray(list as dynamic);
+            Assert.Equal(1, jsonArray.Count);
+
+            Assert.True(jsonArray.Contains(value1Node));
+            Assert.Equal(value1, getter(jsonArray));
+
+            jsonArray.Insert(0, value2 as dynamic);
+            Assert.Equal(2, jsonArray.Count);
+            Assert.True(jsonArray.Contains(value2Node));
+            Assert.Equal(value2, getter(jsonArray));
+
+            Assert.Equal(1, jsonArray.IndexOf(value1Node));
+            Assert.Equal(1, jsonArray.LastIndexOf(value1Node));
+
+            jsonArray.RemoveAt(0);
+            Assert.False(jsonArray.Contains(value2Node));
+
+            jsonArray.Remove(value1Node);
+            Assert.False(jsonArray.Contains(value1Node));
+
+            Assert.Equal(0, jsonArray.Count);
+
+            jsonArray.Add(value2Casted);
+            Assert.Equal(1, jsonArray.Count);
+            Assert.True(jsonArray.Contains(value2Node));
+            Assert.Equal(value2, getter(jsonArray));
+
+            jsonArray[0] = value1Node;
+            Assert.Equal(1, jsonArray.Count);
+            Assert.True(jsonArray.Contains(value1Node));
+            Assert.Equal(value1, getter(jsonArray));
+        }
+
+        [Fact]
+        public static void TestStringArray()
+        {
+            TestArray(
+                "value1", "value2",
+                jsonArray => ((JsonString)jsonArray[0]).Value,
+                v => new JsonString(v)
+            );
+        }
+
+        [Fact]
+        public static void TestBooleanArray()
+        {
+            TestArray(
+                true, false,
+                jsonArray => ((JsonBoolean)jsonArray[0]).Value,
+                v => new JsonBoolean(v)
+            );
+        }
+
+        [Fact]
+        public static void TestByteArray()
+        {
+            TestArray<byte>(
+                byte.MaxValue, byte.MaxValue - 1,
+                jsonArray => ((JsonNumber)jsonArray[0]).GetByte(),
+                v => new JsonNumber(v)
+            );
+        }
+
+        [Fact]
+        public static void TestInt16Array()
+        {
+            TestArray<short>(
+                short.MaxValue, short.MaxValue - 1,
+                jsonArray => ((JsonNumber)jsonArray[0]).GetInt16(),
+                v => new JsonNumber(v)
+            );
+        }
+
+        [Fact]
+        public static void TestInt32rray()
+        {
+            TestArray(
+                int.MaxValue, int.MaxValue - 1,
+                jsonArray => ((JsonNumber)jsonArray[0]).GetInt32(),
+                v => new JsonNumber(v)
+            );
+        }
+
+        [Fact]
+        public static void TestInt64rray()
+        {
+            TestArray(
+                long.MaxValue, long.MaxValue - 1,
+                jsonArray => ((JsonNumber)jsonArray[0]).GetInt64(),
+                v => new JsonNumber(v)
+            );
+        }
+
+        [Fact]
+        public static void TestSingleArray()
+        {
+            TestArray(
+                3.14f, 1.41f,
+                jsonArray => ((JsonNumber)jsonArray[0]).GetSingle(),
+                v => new JsonNumber(v)
+            );
+        }
+
+        [Fact]
+        public static void TestDoubleArray()
+        {
+            TestArray(
+                3.14, 1.41,
+                jsonArray => ((JsonNumber)jsonArray[0]).GetDouble(),
+                v => new JsonNumber(v)
+            );
+        }
+
+        [Fact]
+        public static void TestSByteArray()
+        {
+            TestArray<sbyte>(
+                sbyte.MaxValue, sbyte.MaxValue - 1,
+                jsonArray => ((JsonNumber)jsonArray[0]).GetSByte(),
+                v => new JsonNumber(v)
+            );
+        }
+
+        [Fact]
+        public static void TestUInt16Array()
+        {
+            TestArray<ushort>(
+                ushort.MaxValue, ushort.MaxValue - 1,
+                jsonArray => ((JsonNumber)jsonArray[0]).GetUInt16(),
+                v => new JsonNumber(v)
+            );
+        }
+
+        [Fact]
+        public static void TestUInt32Array()
+        {
+            TestArray(
+                uint.MaxValue, uint.MaxValue - 1,
+                jsonArray => ((JsonNumber)jsonArray[0]).GetUInt32(),
+                v => new JsonNumber(v)
+            );
+        }
+
+        [Fact]
+        public static void TestUInt64Array()
+        {
+            TestArray(
+                ulong.MaxValue, ulong.MaxValue - 1,
+                jsonArray => ((JsonNumber)jsonArray[0]).GetUInt64(),
+                v => new JsonNumber(v)
+            );
+        }
+
+        [Fact]
+        public static void TestDecimalArray()
+        {
+            TestArray(
+                decimal.One, decimal.Zero,
+                jsonArray => ((JsonNumber)jsonArray[0]).GetDecimal(),
+                v => new JsonNumber(v)
+            );
+        }
+
+        [Fact]
+        public static void TestCreatingJsonArrayFromStringArray()
+        {
+            string[] expected = { "sushi", "pasta", "cucumber soup" };
+
+            var dishesJsonArray = new JsonArray(expected);
+            Assert.Equal(3, dishesJsonArray.Count);
+
+            for (int i = 0; i < dishesJsonArray.Count; i++)
+            {
+                Assert.IsType<JsonString>(dishesJsonArray[i]);
+                Assert.Equal(expected[i], dishesJsonArray[i] as JsonString);
+            }
+        }
+
+        [Fact]
+        public static void TestCreatingNestedJsonArray()
+        {
+            var vertices = new JsonArray()
+            {
+                new JsonArray
+                {
+                    new JsonArray
+                    {
+                        new JsonArray { 0, 0, 0 },
+                        new JsonArray { 0, 0, 1 }
+                    },
+                    new JsonArray
+                    {
+                        new JsonArray { 0, 1, 0 },
+                        new JsonArray { 0, 1, 1 }
+                    }
+                },
+                new JsonArray
+                {
+                    new JsonArray
+                    {
+                        new JsonArray { 1, 0, 0 },
+                        new JsonArray { 1, 0, 1 }
+                    },
+                    new JsonArray
+                    {
+                        new JsonArray { 1, 1, 0 },
+                        new JsonArray { 1, 1, 1 }
+                    }
+                },
+            };
+
+            var innerJsonArray = (JsonArray)vertices[0];
+            innerJsonArray = (JsonArray)innerJsonArray[0];
+            Assert.IsType<JsonArray>(innerJsonArray[0]);
+        }
+
+        [Fact]
+        public static void TestCreatingJsonArrayFromCollection()
+        {
+            var employeesIds = new JsonArray(EmployeesDatabase.GetTenBestEmployees().Select(employee => new JsonString(employee.Key)));
+
+            JsonString prevId = new JsonString();
+            foreach (JsonNode employeeId in employeesIds)
+            {
+                var employeeIdString = (JsonString)employeeId;
+                Assert.NotEqual(prevId, employeeIdString);
+                prevId = employeeIdString;
+            }
+        }
+
+        [Fact]
+        public static void TestCreatingJsonArrayFromCollectionOfString()
+        {
+            var employeesIds = new JsonArray(EmployeesDatabase.GetTenBestEmployees().Select(employee => employee.Key));
+
+            JsonString prevId = new JsonString();
+            foreach (JsonNode employeeId in employeesIds)
+            {
+                var employeeIdString = (JsonString)employeeId;
+                Assert.NotEqual(prevId, employeeIdString);
+                prevId = employeeIdString;
+            }
+        }
+
+        [Fact]
+        public static void TestAddingToJsonArray()
+        {
+            var employeesIds = new JsonArray();
+
+            foreach (KeyValuePair<string, JsonNode> employee in EmployeesDatabase.GetTenBestEmployees())
+            {
+                employeesIds.Add(employee.Key);
+            }
+
+            JsonString prevId = new JsonString();
+            foreach (JsonNode employeeId in employeesIds)
+            {
+                var employeeIdString = (JsonString)employeeId;
+                Assert.NotEqual(prevId, employeeIdString);
+                prevId = employeeIdString;
+            }
+        }
+
+        [Fact]
+        public static void TestAccesingNestedJsonArrayGetPropertyMethod()
+        {
+            var issues = new JsonObject()
+            {
+                { "features", new JsonString [] { "new functionality 1", "new functionality 2" } },
+                { "bugs", new JsonString [] { "bug 123", "bug 4566", "bug 821" } },
+                { "tests", new JsonString [] { "code coverage" } },
+            };
+
+            issues.GetJsonArrayPropertyValue("bugs").Add("bug 12356");
+            ((JsonString)issues.GetJsonArrayPropertyValue("features")[0]).Value = "feature 1569";
+            ((JsonString)issues.GetJsonArrayPropertyValue("features")[1]).Value = "feature 56134";
+
+            Assert.Equal((JsonString)"bug 12356", ((JsonArray)issues["bugs"])[3]);
+            Assert.Equal("feature 1569", (JsonString)((JsonArray)issues["features"])[0]);
+            Assert.Equal("feature 56134", (JsonString)((JsonArray)issues["features"])[1]);
+        }
+
+        [Fact]
+        public static void TestAccesingNestedJsonArrayTryGetPropertyMethod()
+        {
+            var issues = new JsonObject()
+            {
+                { "features", new JsonString [] { "new functionality 1", "new functionality 2" } },
+            };
+
+            Assert.True(issues.TryGetJsonArrayPropertyValue("features", out JsonArray featuresArray));
+            Assert.Equal("new functionality 1", (JsonString)featuresArray[0]);
+            Assert.Equal("new functionality 2", (JsonString)featuresArray[1]);
+        }
+
+        [Fact]
+        public static void TestOutOfRangeException()
+        {
+            Assert.Throws<ArgumentOutOfRangeException>(() => new JsonArray()[-1]);
+            Assert.Throws<ArgumentOutOfRangeException>(() => new JsonArray()[-1] = new JsonString());
+            Assert.Throws<ArgumentOutOfRangeException>(() => new JsonArray()[0]);
+            Assert.Throws<ArgumentOutOfRangeException>(() => new JsonArray()[0] = new JsonString());
+            Assert.Throws<ArgumentOutOfRangeException>(() => new JsonArray()[1]);
+            Assert.Throws<ArgumentOutOfRangeException>(() => new JsonArray()[1] = new JsonString());
+        }
+
+        [Fact]
+        public static void TestIsReadOnly()
+        {
+            Assert.False(new JsonArray().IsReadOnly);
+        }
+
+        [Fact]
+        public static void TestClean()
+        {
+            var jsonArray = new JsonArray { 1, 2, 3 };
+            
+            Assert.Equal(3, jsonArray.Count);
+            Assert.Equal((JsonNumber)1, jsonArray[0]);
+            Assert.Equal((JsonNumber)2, jsonArray[1]);
+            Assert.Equal((JsonNumber)3, jsonArray[2]);
+
+            jsonArray.Clear();
+            
+            Assert.Equal(0, jsonArray.Count);
+        }
+
+        [Fact]
+        public static void TestRemoveAll()
+        {
+            var jsonArray = new JsonArray { 1, 2, 3 };
+
+            Assert.Equal(3, jsonArray.Count);
+            Assert.Equal((JsonNumber)1, jsonArray[0]);
+            Assert.Equal((JsonNumber)2, jsonArray[1]);
+            Assert.Equal((JsonNumber)3, jsonArray[2]);
+
+            jsonArray.RemoveAll(v => ((JsonNumber)v).GetInt32() <= 2);
+
+            Assert.Equal(1, jsonArray.Count);
+            Assert.Equal((JsonNumber)3, jsonArray[0]);
+        }       
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/JsonNodeCloneTests.cs b/src/libraries/System.Text.Json/tests/JsonNodeCloneTests.cs
new file mode 100644 (file)
index 0000000..6ed89ed
--- /dev/null
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Xunit;
+
+namespace System.Text.Json.Tests
+{
+    public static class JsonNodeCloneTests
+    { 
+        [Fact]
+        public static void TestCloneJsonArray()
+        {
+            var jsonArray = new JsonArray { "value1", "value2" };
+            var jsonArrayCopy = jsonArray.Clone() as JsonArray;
+            Assert.Equal(2, jsonArrayCopy.Count);
+            jsonArray.Add("value3");
+            Assert.Equal(2, jsonArrayCopy.Count);
+        }
+
+        [Fact]
+        public static void TestDeepCloneJsonArray()
+        {
+            JsonArray inner = new JsonArray { 1, 2, 3 };
+            JsonArray outer = new JsonArray { inner };
+            JsonArray outerClone = (JsonArray)outer.Clone();
+            ((JsonArray) outerClone[0]).Add(4);
+
+            Assert.Equal(3, inner.Count);
+        }
+
+        [Fact]
+        public static void TestCloneJsonNode()
+        {
+            var jsonObject = new JsonObject
+            {
+                { "text", "property value" },
+                { "boolean", true },
+                { "number", 15 },
+                { "array", new JsonString[] { "value1", "value2"} }
+            };
+
+            var jsonObjectCopy = (JsonObject)jsonObject.Clone();
+
+            jsonObject["text"] = (JsonString)"something different";
+            Assert.Equal("property value", (JsonString)jsonObjectCopy["text"]);
+
+            ((JsonBoolean)jsonObject["boolean"]).Value = false;
+            Assert.True(((JsonBoolean)jsonObjectCopy["boolean"]).Value);
+
+            Assert.Equal(2, jsonObjectCopy.GetJsonArrayPropertyValue("array").Count);
+            jsonObject.GetJsonArrayPropertyValue("array").Add("value3");
+            Assert.Equal(2, jsonObjectCopy.GetJsonArrayPropertyValue("array").Count);
+
+            jsonObject.Add("new one", 123);
+            Assert.Equal(4, jsonObjectCopy.PropertyNames.Count);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/JsonNodeParseTests.cs b/src/libraries/System.Text.Json/tests/JsonNodeParseTests.cs
new file mode 100644 (file)
index 0000000..426772e
--- /dev/null
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Xunit;
+
+namespace System.Text.Json.Tests
+{
+    public static class JsonNodeParseTests
+    {
+        private static string jsonSampleString = @"
+            {
+                ""text"": ""property value"",
+                ""boolean true"": true,
+                ""boolean false"": false,
+                ""null"": null,
+                ""int"": 17,
+                ""double"": 3.14,
+                ""scientific"": 3e100,
+                ""array"" : [1,2,3],
+                ""inner object"" : 
+                {
+                    ""inner property"" : ""value""
+                }
+            }";
+
+        [Fact]
+        public static void TestParseStringToJsonNode()
+        {
+            JsonNode node = JsonNode.Parse(jsonSampleString);
+
+            var jsonObject = (JsonObject)node;
+            Assert.Equal(9, jsonObject.PropertyNames.Count);
+            Assert.Equal(9, jsonObject.PropertyValues.Count);
+            Assert.Equal("property value", (JsonString)jsonObject["text"]);
+            Assert.True(((JsonBoolean)jsonObject["boolean true"]).Value);
+            Assert.False(((JsonBoolean)jsonObject["boolean false"]).Value);
+            Assert.Null(jsonObject["null"]);
+            Assert.Equal(17, ((JsonNumber)jsonObject["int"]).GetInt32());
+            Assert.Equal(3.14, ((JsonNumber)jsonObject["double"]).GetDouble());
+            Assert.Equal("3e100", ((JsonNumber)jsonObject["scientific"]).ToString());
+
+            var innerArray = (JsonArray)jsonObject["array"];
+            Assert.Equal(3, innerArray.Count);
+            Assert.Equal(1, ((JsonNumber)innerArray[0]).GetInt32());
+            Assert.Equal(2, ((JsonNumber)innerArray[1]).GetInt32());
+            Assert.Equal(3, ((JsonNumber)innerArray[2]).GetInt32());
+
+            var innerObject = (JsonObject)jsonObject["inner object"];
+            Assert.Equal(1, innerObject.PropertyNames.Count);
+            Assert.Equal(1, innerObject.PropertyValues.Count);
+            Assert.Equal("value", (JsonString)innerObject["inner property"]);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/JsonNodeTestData.cs b/src/libraries/System.Text.Json/tests/JsonNodeTestData.cs
new file mode 100644 (file)
index 0000000..39d443c
--- /dev/null
@@ -0,0 +1,111 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace System.Text.Json.Tests
+{
+    /// <summary>
+    /// Helper class simulating external library
+    /// </summary>
+    internal static class EmployeesDatabase
+    {
+        private static int s_id = 0;
+        public static KeyValuePair<string, JsonNode> GetNextEmployee()
+        {
+            var employee = new JsonObject()
+            {
+                { "name", "John" } ,
+                { "surname", "Smith"},
+                { "age", 45 }
+            };
+
+            return new KeyValuePair<string, JsonNode>("employee" + s_id++, employee);
+        }
+
+        public static IEnumerable<KeyValuePair<string, JsonNode>> GetTenBestEmployees()
+        {
+            for (int i = 0; i < 10; i++)
+                yield return GetNextEmployee();
+        }
+
+        /// <summary>
+        /// Returns following JsonObject:
+        /// {
+        ///     { "name" : "John" }
+        ///     { "phone numbers" : { "work" :  "425-555-0123", "home": "425-555-0134"  } }
+        ///     { 
+        ///         "reporting employees" : 
+        ///         {
+        ///             "software developers" :
+        ///             {
+        ///                 "full time employees" : /JsonObject of 3 employees fromk database/ 
+        ///                 "intern employees" : /JsonObject of 2 employees fromk database/ 
+        ///             },
+        ///             "HR" : /JsonObject of 10 employees fromk database/ 
+        ///         }
+        /// </summary>
+        /// <returns></returns>
+        public static JsonObject GetManager()
+        {
+            var manager = GetNextEmployee().Value as JsonObject;
+
+            manager.Add
+            (
+                "phone numbers",
+                new JsonObject()
+                {
+                    { "work", "425-555-0123" }, { "home", "425-555-0134" }
+                }
+            );
+
+            manager.Add
+            (
+                "reporting employees", new JsonObject()
+                {
+                    {
+                        "software developers", new JsonObject()
+                        {
+                            {
+                                "full time employees", new JsonObject()
+                                {
+                                    EmployeesDatabase.GetNextEmployee(),
+                                    EmployeesDatabase.GetNextEmployee(),
+                                    EmployeesDatabase.GetNextEmployee(),
+                                }
+                            },
+                            {
+                                "intern employees", new JsonObject()
+                                {
+                                    EmployeesDatabase.GetNextEmployee(),
+                                    EmployeesDatabase.GetNextEmployee(),
+                                }
+                            }
+                        }
+                    },
+                    {
+                        "HR", new JsonObject()
+                        {
+                            {
+                                "full time employees", new JsonObject(EmployeesDatabase.GetTenBestEmployees())
+                            }
+                        }
+                    }
+                }
+            );
+
+            return manager;
+        }
+    }
+
+    /// <summary>
+    /// Helper class simulating enum
+    /// </summary>
+    internal enum AvailableStateCodes
+    {
+        WA,
+        CA,
+        NY,
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/JsonNodeToJsonElementTests.cs b/src/libraries/System.Text.Json/tests/JsonNodeToJsonElementTests.cs
new file mode 100644 (file)
index 0000000..dc222e4
--- /dev/null
@@ -0,0 +1,112 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using Xunit;
+
+namespace System.Text.Json.Tests
+{
+    public static class JsonNodeToJsonElementTests
+    {
+        [Fact]
+        public static void TestAsJsonElement()
+        {
+            var jsonObject = new JsonObject
+            {
+                { "text", "property value" },
+                { "boolean", true },
+                { "number", 15 },
+                { "null node", (JsonNode) null },
+                { "array", new JsonString[] { "value1", "value2"} }
+            };
+
+            JsonElement jsonElement = jsonObject.AsJsonElement();
+            Assert.False(jsonElement.IsImmutable);
+
+            JsonElement.ObjectEnumerator enumerator = jsonElement.EnumerateObject();
+
+            enumerator.MoveNext();
+            Assert.Equal("text", enumerator.Current.Name);
+            Assert.Equal(JsonValueKind.String, enumerator.Current.Value.ValueKind);
+            Assert.Equal("property value", enumerator.Current.Value.GetString());
+           
+            enumerator.MoveNext();
+            Assert.Equal("boolean", enumerator.Current.Name);
+            Assert.Equal(JsonValueKind.True, enumerator.Current.Value.ValueKind);
+            Assert.True(enumerator.Current.Value.GetBoolean());
+            
+            enumerator.MoveNext();
+            Assert.Equal("number", enumerator.Current.Name);
+            Assert.Equal(15, enumerator.Current.Value.GetInt32());
+            Assert.Equal(JsonValueKind.Number, enumerator.Current.Value.ValueKind);
+
+            enumerator.MoveNext();
+            Assert.Equal("null node", enumerator.Current.Name);
+            Assert.Equal(JsonValueKind.Null, enumerator.Current.Value.ValueKind);
+
+            enumerator.MoveNext();
+            Assert.Equal("array", enumerator.Current.Name);
+            Assert.Equal(2, enumerator.Current.Value.GetArrayLength());
+            Assert.Equal(JsonValueKind.Array, enumerator.Current.Value.ValueKind);
+            JsonElement.ArrayEnumerator innerEnumerator = enumerator.Current.Value.EnumerateArray();
+
+            innerEnumerator.MoveNext();
+            Assert.Equal(JsonValueKind.String, innerEnumerator.Current.ValueKind);
+            Assert.Equal("value1", innerEnumerator.Current.GetString());
+            
+            innerEnumerator.MoveNext();
+            Assert.Equal(JsonValueKind.String, innerEnumerator.Current.ValueKind);
+            Assert.Equal("value2", innerEnumerator.Current.GetString());
+
+            Assert.False(innerEnumerator.MoveNext());
+            innerEnumerator.Dispose();
+
+            Assert.False(enumerator.MoveNext());
+            enumerator.Dispose();
+
+            // Modifying JsonObject will change JsonElement:
+
+            jsonObject["text"] = new JsonNumber("123");
+            Assert.Equal(123, jsonElement.GetProperty("text").GetInt32());
+        }
+
+        [Fact]
+        public static void TestArrayIterator()
+        {
+            JsonArray array = new JsonArray { 1, 2, 3 };
+            JsonElement jsonNodeElement = array.AsJsonElement();
+            IEnumerator enumerator = jsonNodeElement.EnumerateArray();
+            array.Add(4);
+            Assert.Throws<InvalidOperationException>(() => enumerator.MoveNext());
+        }
+
+        [Fact]
+        public static void TestGetNode()
+        {
+            var jsonObject = new JsonObject
+            {
+                { "text", "property value" },
+                { "boolean", true },
+                { "number", 15 },
+                { "null node", (JsonNode) null },
+                { "array", new JsonString[] { "value1", "value2"} }
+            };
+
+            JsonElement jsonElement = jsonObject.AsJsonElement();
+            JsonObject jsonObjectFromElement = (JsonObject)JsonNode.GetNode(jsonElement);
+
+            Assert.Equal("property value", (JsonString)jsonObjectFromElement["text"]);
+
+            // Modifying JsonObject will change JsonObjectFromElement:
+
+            jsonObject["text"] = new JsonString("something different");
+            Assert.Equal("something different", (JsonString)jsonObjectFromElement["text"]);
+
+            // Modifying JsonObjectFromElement will change JsonObject:
+
+            ((JsonBoolean)jsonObjectFromElement["boolean"]).Value = false;
+            Assert.False(((JsonBoolean)jsonObject["boolean"]).Value);
+        }
+    }
+}
diff --git a/src/libraries/System.Text.Json/tests/JsonObjectTests.TestData.cs b/src/libraries/System.Text.Json/tests/JsonObjectTests.TestData.cs
deleted file mode 100644 (file)
index fb324ed..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections.Generic;
-
-namespace System.Text.Json.Tests
-{
-    public static partial class JsonObjectTests
-    {
-        /// <summary>
-        /// Helper class simulating external library
-        /// </summary>
-        private static class EmployeesDatabase
-        {
-            private static int s_id = 0;
-            public static KeyValuePair<string, JsonNode> GetNextEmployee()
-            {
-                var employee = new JsonObject()
-                {
-                    { "name", "John" } ,
-                    { "surname", "Smith"},
-                    { "age", 45 }
-                };
-
-                return new KeyValuePair<string, JsonNode>("employee" + s_id++, employee);
-            }
-
-            public static IEnumerable<KeyValuePair<string, JsonNode>> GetTenBestEmployees()
-            {
-                for (int i = 0; i < 10; i++)
-                    yield return GetNextEmployee();
-            }
-
-            /// <summary>
-            /// Returns following JsonObject:
-            /// {
-            ///     { "name" : "John" }
-            ///     { "phone numbers" : { "work" :  "123-456-7890", "home": "123-456-7890"  } }
-            ///     { 
-            ///         "reporting employees" : 
-            ///         {
-            ///             "software developers" :
-            ///             {
-            ///                 "full time employees" : /JsonObject of 3 employees fromk database/ 
-            ///                 "intern employees" : /JsonObject of 2 employees fromk database/ 
-            ///             },
-            ///             "HR" : /JsonObject of 10 employees fromk database/ 
-            ///         }
-            /// </summary>
-            /// <returns></returns>
-            public static JsonObject GetManager()
-            {
-                var manager = GetNextEmployee().Value as JsonObject;
-
-                manager.Add
-                (
-                    "phone numbers",
-                    new JsonObject()
-                    {
-                        { "work", "123-456-7890" }, { "home", "123-456-7890" }
-                    }
-                );
-
-                manager.Add
-                (
-                    "reporting employees", new JsonObject()
-                    {
-                        {
-                            "software developers", new JsonObject()
-                            {
-                                {
-                                    "full time employees", new JsonObject()
-                                    {
-                                        EmployeesDatabase.GetNextEmployee(),
-                                        EmployeesDatabase.GetNextEmployee(),
-                                        EmployeesDatabase.GetNextEmployee(),
-                                    }
-                                },
-                                {
-                                    "intern employees", new JsonObject()
-                                    {
-                                        EmployeesDatabase.GetNextEmployee(),
-                                        EmployeesDatabase.GetNextEmployee(),
-                                    }
-                                }
-                            }
-                        },
-                        {
-                            "HR", new JsonObject()
-                            {
-                                {
-                                    "full time employees", new JsonObject(EmployeesDatabase.GetTenBestEmployees())
-                                }
-                            }
-                        }
-                    }
-                );
-
-                return manager;
-            }
-            public static void PerformHeavyOperations(JsonElement employee) { }
-        }
-
-        private static class HealthCare
-        {
-            public static void CreateMedicalAppointment(string personName) { }
-        }
-
-        /// <summary>
-        /// Helper class simulating enum
-        /// </summary>
-        private enum AvailableStateCodes
-        {
-            WA,
-            CA,
-            NY,
-        }
-    }
-}
index 9759d05..392eaf5 100644 (file)
@@ -3,11 +3,13 @@
 // See the LICENSE file in the project root for more information.
 
 using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
 using Xunit;
 
 namespace System.Text.Json.Tests
 {
-    public static partial class JsonObjectTests
+    public static class JsonObjectTests
     {
         [Fact]
         public static void TestDefaultConstructor()
@@ -131,20 +133,29 @@ namespace System.Text.Json.Tests
             Assert.Equal(guidString, (JsonString)jsonObject["guid"]);
         }
 
-        [Fact]
-        public static void TestDateTime()
+        public static IEnumerable<object[]> DateTimeData =>
+            new List<object[]>
+            {
+                new object[] { new DateTime(DateTime.MinValue.Ticks, DateTimeKind.Utc) },
+                new object[] { new DateTime(2019, 1, 1) },
+                new object[] { new DateTime(2019, 1, 1, new GregorianCalendar()) },
+                new object[] { new DateTime(2019, 1, 1, new ChineseLunisolarCalendar()) }
+            };
+
+        [Theory]
+        [MemberData(nameof(DateTimeData))]
+        public static void TestDateTime(DateTime dateTime)
         {
-            DateTime dateTime = new DateTime(DateTime.MinValue.Ticks);
             var jsonObject = new JsonObject { { "dateTime", dateTime } };
-            Assert.Equal(dateTime.ToString(), (JsonString)jsonObject["dateTime"]);
+            Assert.Equal(dateTime.ToString("s", CultureInfo.InvariantCulture), (JsonString)jsonObject["dateTime"]);
         }
 
-        [Fact]
-        public static void TestDateTimeOffset()
+        [Theory]
+        [MemberData(nameof(DateTimeData))]
+        public static void TestDateTimeOffset(DateTimeOffset dateTimeOffset)
         {
-            DateTimeOffset dateTimeOffset = new DateTime(DateTime.MinValue.Ticks, DateTimeKind.Utc);
             var jsonObject = new JsonObject { { "dateTimeOffset", dateTimeOffset } };
-            Assert.Equal(dateTimeOffset.ToString(), (JsonString)jsonObject["dateTimeOffset"]);
+            Assert.Equal(dateTimeOffset.ToString("s", CultureInfo.InvariantCulture), (JsonString)jsonObject["dateTimeOffset"]);
         }
 
         [Fact]
@@ -295,6 +306,104 @@ namespace System.Text.Json.Tests
         }
 
         [Fact]
+        public static void TestAddingJsonArrayFromJsonNumberArray()
+        {
+            var preferences = new JsonObject()
+            {
+                { "prime numbers", new JsonNumber[] { 19, 37 } }
+            };
+
+            var primeNumbers = (JsonArray)preferences["prime numbers"];
+            Assert.Equal(2, primeNumbers.Count);
+
+            int[] expected = { 19, 37 };
+
+            for (int i = 0; i < primeNumbers.Count; i++)
+            {
+                Assert.IsType<JsonNumber>(primeNumbers[i]);
+                Assert.Equal(expected[i], primeNumbers[i] as JsonNumber);
+            }
+        }
+
+        [Fact]
+        public static void TestAddingJsonArray()
+        {
+            var preferences = new JsonObject()
+            {
+                { "colours", (JsonNode) new JsonArray{ "red", "green", "blue" } }
+            };
+
+            var colours = (JsonArray)preferences["colours"];
+            Assert.Equal(3, colours.Count);
+
+            string[] expected = { "red", "green", "blue" };
+
+            for (int i = 0; i < colours.Count; i++)
+            {
+                Assert.IsType<JsonString>(colours[i]);
+                Assert.Equal(expected[i], colours[i] as JsonString);
+            }
+        }
+
+        [Fact]
+        public static void TestAddingJsonArrayFromIEnumerableOfStrings()
+        {
+            var sportsExperienceYears = new JsonObject()
+            {
+                { "skiing", 5 },
+                { "cycling", 8 },
+                { "hiking", 6 },
+                { "chess", 2 },
+                { "skating", 1 },
+            };
+
+            // choose only sports with > 2 experience years
+            IEnumerable<string> sports = sportsExperienceYears.Where(sport => ((JsonNumber)sport.Value).GetInt32() > 2).Select(sport => sport.Key);
+
+            var preferences = new JsonObject()
+            {
+                { "sports", (JsonNode) new JsonArray(sports) }
+            };
+
+            var sportsJsonArray = (JsonArray)preferences["sports"];
+            Assert.Equal(3, sportsJsonArray.Count);
+
+            for (int i = 0; i < sportsJsonArray.Count; i++)
+            {
+                Assert.IsType<JsonString>(sportsJsonArray[i]);
+                Assert.Equal(sports.ElementAt(i), sportsJsonArray[i] as JsonString);
+            }
+        }
+
+        [Fact]
+        public static void TestAddingJsonArrayFromIEnumerableOfJsonNodes()
+        {
+            var strangeWords = new JsonArray()
+            {
+                "supercalifragilisticexpialidocious",
+                "gladiolus",
+                "albumen",
+                "smaragdine"
+            };
+
+            var preferences = new JsonObject()
+            {
+                { "strange words", strangeWords.Where(word => ((JsonString)word).Value.Length < 10) }
+            };
+
+            var strangeWordsJsonArray = (JsonArray)preferences["strange words"];
+            Assert.Equal(2, strangeWordsJsonArray.Count);
+
+            string[] expected = { "gladiolus", "albumen" };
+
+            for (int i = 0; i < strangeWordsJsonArray.Count; i++)
+            {
+                Assert.IsType<JsonString>(strangeWordsJsonArray[i]);
+                Assert.Equal(expected[i], strangeWordsJsonArray[i] as JsonString);
+            }
+        }
+
+        [Fact]
         public static void TestContains()
         {
             var person = new JsonObject
@@ -507,7 +616,7 @@ namespace System.Text.Json.Tests
         [Fact]
         public static void TestGetJsonObjectPropertyThrows()
         {
-            Assert.Throws<InvalidCastException>(() =>
+            Assert.Throws<ArgumentException>(() =>
             {
                 var jsonObject = new JsonObject()
                 {
@@ -534,6 +643,35 @@ namespace System.Text.Json.Tests
         }
 
         [Fact]
+        public static void TestGetJsonArrayPropertyThrows()
+        {
+            Assert.Throws<ArgumentException>(() =>
+            {
+                var jsonObject = new JsonObject()
+                {
+                    { "name", "value" }
+                };
+
+                jsonObject.GetJsonArrayPropertyValue("name");
+            });
+        }
+
+        [Fact]
+        public static void TestTryGetArrayPropertyFails()
+        {
+            var jsonObject = new JsonObject()
+            {
+                { "name", "value" }
+            };
+
+            Assert.False(jsonObject.TryGetJsonArrayPropertyValue("name", out JsonArray property));
+            Assert.Null(property);
+
+            Assert.False(jsonObject.TryGetJsonArrayPropertyValue("other", out property));
+            Assert.Null(property);
+        }
+
+        [Fact]
         public static void TestArgumentNullValidation()
         {
             Assert.Throws<ArgumentNullException>(() => new JsonObject().Add(null, ""));
index 422c214..7644fd9 100644 (file)
@@ -2,6 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Collections.Generic;
+using System.Globalization;
 using Xunit;
 
 namespace System.Text.Json.Tests
@@ -79,20 +81,29 @@ namespace System.Text.Json.Tests
             Assert.Equal(guidString, jsonString);
         }
 
-        [Fact]
-        public static void TestDateTime()
+        public static IEnumerable<object[]> DateTimeData =>
+           new List<object[]>
+           {
+                       new object[] { new DateTime(DateTime.MinValue.Ticks, DateTimeKind.Utc) },
+                       new object[] { new DateTime(2019, 1, 1) },
+                       new object[] { new DateTime(2019, 1, 1, new GregorianCalendar()) },
+                       new object[] { new DateTime(2019, 1, 1, new ChineseLunisolarCalendar()) }
+           };
+
+        [Theory]
+        [MemberData(nameof(DateTimeData))]
+        public static void TestDateTime(DateTime dateTime)
         {
-            DateTime dateTime = new DateTime(DateTime.MinValue.Ticks);
             var jsonString = new JsonString(dateTime);
-            Assert.Equal(dateTime.ToString(), jsonString);
+            Assert.Equal(dateTime.ToString("s", CultureInfo.InvariantCulture), jsonString);
         }
 
-        [Fact]
-        public static void TestDateTimeOffset()
+        [Theory]
+        [MemberData(nameof(DateTimeData))]
+        public static void TestDateTimeOffset(DateTimeOffset dateTimeOffset)
         {
-            DateTimeOffset dateTimeOffset = new DateTime(DateTime.MinValue.Ticks, DateTimeKind.Utc);
             var jsonString = new JsonString(dateTimeOffset);
-            Assert.Equal(dateTimeOffset.ToString(), jsonString);
+            Assert.Equal(dateTimeOffset.ToString("s", CultureInfo.InvariantCulture), jsonString);
         }
 
         [Fact]
index c07242a..d06ef98 100644 (file)
@@ -21,6 +21,8 @@
     <Compile Include="JsonElementWriteTests.cs" />
     <Compile Include="JsonEncodedTextTests.cs" />
     <Compile Include="JsonGuidTestData.cs" />
+    <Compile Include="JsonNodeToJsonElementTests.cs" />
+    <Compile Include="JsonNodeCloneTests.cs" />
     <Compile Include="JsonNumberTestData.cs" />
     <Compile Include="JsonPropertyTests.cs" />
     <Compile Include="JsonReaderStateAndOptionsTests.cs" />
     <PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="JsonArrayTests.cs" />
     <Compile Include="JsonBooleanTests.cs" />
+    <Compile Include="JsonNodeTestData.cs" />
+    <Compile Include="JsonNodeParseTests.cs" />
     <Compile Include="JsonNumberTests.cs" />
     <Compile Include="JsonObjectTests.cs" />
-    <Compile Include="JsonObjectTests.TestData.cs" />
     <Compile Include="JsonStringTests.cs" />
   </ItemGroup>
 </Project>