// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; using Xunit; using Xunit.Extensions; namespace System.Json { /// /// JsonValue unit tests /// public class JsonValueTests { public static IEnumerable StreamLoadingTestData { get { bool[] useSeekableStreams = new bool[] { true, false }; Dictionary allEncodings = new Dictionary { { "UTF8, no BOM", new UTF8Encoding(false) }, { "Unicode, no BOM", new UnicodeEncoding(false, false) }, { "BigEndianUnicode, no BOM", new UnicodeEncoding(true, false) }, }; string[] jsonStrings = { "[1, 2, null, false, {\"foo\": 1, \"bar\":true, \"baz\":null}, 1.23e+56]", "4" }; foreach (string jsonString in jsonStrings) { foreach (bool useSeekableStream in useSeekableStreams) { foreach (var kvp in allEncodings) { yield return new object[] { jsonString, useSeekableStream, kvp.Key, kvp.Value }; } } } } } /// /// Tests for . /// [Theory] [PropertyData("StreamLoadingTestData")] public void StreamLoading(string jsonString, bool useSeekableStream, string encodingName, Encoding encoding) { using (MemoryStream ms = new MemoryStream()) { StreamWriter sw = new StreamWriter(ms, encoding); sw.Write(jsonString); sw.Flush(); Log.Info("[{0}] {1}: size of the json stream: {2}", useSeekableStream ? "seekable" : "non-seekable", encodingName, ms.Position); ms.Position = 0; JsonValue parsed = JsonValue.Parse(jsonString); JsonValue loaded = useSeekableStream ? JsonValue.Load(ms) : JsonValue.Load(new NonSeekableStream(ms)); using (StringReader sr = new StringReader(jsonString)) { JsonValue loadedFromTextReader = JsonValue.Load(sr); Assert.Equal(parsed.ToString(), loaded.ToString()); Assert.Equal(parsed.ToString(), loadedFromTextReader.ToString()); } } } [Fact] public void ZeroedStreamLoadingThrowsFormatException() { ExpectException(delegate { using (MemoryStream ms = new MemoryStream(new byte[10])) { JsonValue.Load(ms); } }); } /// /// Tests for handling with escaped characters. /// [Fact] public void EscapedCharacters() { string str = null; JsonValue value = null; str = (string)value; Assert.Null(str); value = "abc\b\t\r\u1234\uDC80\uDB11def\\\0ghi"; str = (string)value; Assert.Equal("\"abc\\u0008\\u0009\\u000d\u1234\\udc80\\udb11def\\\\\\u0000ghi\"", value.ToString()); value = '\u0000'; str = (string)value; Assert.Equal("\u0000", str); } /// /// Tests for JSON objects with the special '__type' object member. /// [Fact] public void TypeHintAttributeTests() { string json = "{\"__type\":\"TypeHint\",\"a\":123}"; JsonValue jv = JsonValue.Parse(json); string newJson = jv.ToString(); Assert.Equal(json, newJson); json = "{\"b\":567,\"__type\":\"TypeHint\",\"a\":123}"; jv = JsonValue.Parse(json); newJson = jv.ToString(); Assert.Equal(json, newJson); json = "[12,{\"__type\":\"TypeHint\",\"a\":123,\"obj\":{\"__type\":\"hint2\",\"b\":333}},null]"; jv = JsonValue.Parse(json); newJson = jv.ToString(); Assert.Equal(json, newJson); } /// /// Tests for reading JSON with different member names. /// [Fact] public void ObjectNameTests() { string[] objectNames = new string[] { "simple", "with spaces", "with<>brackets", "", }; foreach (string objectName in objectNames) { string json = String.Format(CultureInfo.InvariantCulture, "{{\"{0}\":123}}", objectName); JsonValue jv = JsonValue.Parse(json); Assert.Equal(123, jv[objectName].ReadAs()); string newJson = jv.ToString(); Assert.Equal(json, newJson); JsonObject jo = new JsonObject { { objectName, 123 } }; Assert.Equal(123, jo[objectName].ReadAs()); newJson = jo.ToString(); Assert.Equal(json, newJson); } ExpectException(() => JsonValue.Parse("{\"nonXmlChar\u0000\":123}")); } /// /// Miscellaneous tests for parsing JSON. /// [Fact] public void ParseMiscellaneousTest() { string[] jsonValues = { "[]", "[1]", "[1,2,3,[4.1,4.2],5]", "{}", "{\"a\":1}", "{\"a\":1,\"b\":2,\"c\":3,\"d\":4}", "{\"a\":1,\"b\":[2,3],\"c\":3}", "{\"a\":1,\"b\":2,\"c\":[1,2,3,[4.1,4.2],5],\"d\":4}", "{\"a\":1,\"b\":[2.1,2.2],\"c\":3,\"d\":4,\"e\":[4.1,4.2,4.3,[4.41,4.42],4.4],\"f\":5}", "{\"a\":1,\"b\":[2.1,2.2,[[[{\"b1\":2.21}]]],2.3],\"c\":{\"d\":4,\"e\":[4.1,4.2,4.3,[4.41,4.42],4.4],\"f\":5}}" }; foreach (string json in jsonValues) { JsonValue jv = JsonValue.Parse(json); Log.Info("{0}", jv.ToString()); string jvstr = jv.ToString(); Assert.Equal(json, jvstr); } } /// /// Negative tests for parsing "unbalanced" JSON (i.e., JSON documents which aren't properly closed). /// [Fact] public void ParseUnbalancedJsonTest() { string[] jsonValues = { "[", "[1,{]", "[1,2,3,{{}}", "}", "{\"a\":}", "{\"a\":1,\"b\":[,\"c\":3,\"d\":4}", "{\"a\":1,\"b\":[2,\"c\":3}", "{\"a\":1,\"b\":[2.1,2.2,\"c\":3,\"d\":4,\"e\":[4.1,4.2,4.3,[4.41,4.42],4.4],\"f\":5}", "{\"a\":1,\"b\":[2.1,2.2,[[[[{\"b1\":2.21}]]],\"c\":{\"d\":4,\"e\":[4.1,4.2,4.3,[4.41,4.42],4.4],\"f\":5}}" }; foreach (string json in jsonValues) { Log.Info("Testing unbalanced JSON: {0}", json); ExpectException(() => JsonValue.Parse(json)); } } /// /// Test for parsing a deeply nested JSON object. /// [Fact] public void ParseDeeplyNestedJsonObjectString() { StringBuilder builderExpected = new StringBuilder(); builderExpected.Append('{'); int depth = 10000; for (int i = 0; i < depth; i++) { string key = i.ToString(CultureInfo.InvariantCulture); builderExpected.AppendFormat("\"{0}\":{{", key); } for (int i = 0; i < depth + 1; i++) { builderExpected.Append('}'); } string json = builderExpected.ToString(); JsonValue jsonValue = JsonValue.Parse(json); string jvstr = jsonValue.ToString(); Assert.Equal(json, jvstr); } /// /// Test for parsing a deeply nested JSON array. /// [Fact] public void ParseDeeplyNestedJsonArrayString() { StringBuilder builderExpected = new StringBuilder(); builderExpected.Append('['); int depth = 10000; for (int i = 0; i < depth; i++) { builderExpected.Append('['); } for (int i = 0; i < depth + 1; i++) { builderExpected.Append(']'); } string json = builderExpected.ToString(); JsonValue jsonValue = JsonValue.Parse(json); string jvstr = jsonValue.ToString(); Assert.Equal(json, jvstr); } /// /// Test for parsing a deeply nested JSON graph, containing both objects and arrays. /// [Fact] public void ParseDeeplyNestedJsonString() { StringBuilder builderExpected = new StringBuilder(); builderExpected.Append('{'); int depth = 10000; for (int i = 0; i < depth; i++) { string key = i.ToString(CultureInfo.InvariantCulture); builderExpected.AppendFormat("\"{0}\":[{{", key); } for (int i = 0; i < depth; i++) { builderExpected.Append("}]"); } builderExpected.Append('}'); string json = builderExpected.ToString(); JsonValue jsonValue = JsonValue.Parse(json); string jvstr = jsonValue.ToString(); Assert.Equal(json, jvstr); } internal static void ExpectException(Action action) where T : Exception { ExpectException(action, null); } internal static void ExpectException(Action action, string partOfExceptionString) where T : Exception { try { action(); Assert.False(true, "This should have thrown"); } catch (T e) { if (partOfExceptionString != null) { Assert.True(e.Message.Contains(partOfExceptionString)); } } } internal class NonSeekableStream : Stream { Stream innerStream; public NonSeekableStream(Stream innerStream) { this.innerStream = innerStream; } public override bool CanRead { get { return true; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return false; } } public override long Position { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } } public override long Length { get { throw new NotSupportedException(); } } public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) { return this.innerStream.Read(buffer, offset, count); } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } } } }