// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; using System.Text; using System.Threading.Tasks; using Microsoft.TestCommon; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Xunit; using Xunit.Extensions; namespace System.Net.Http.Formatting { public class JsonNetSerializationTest { public static IEnumerable SerializedJson { get { return new TheoryDataSet() { // Primitives { 'f', "\"f\"" }, { "abc", "\"abc\"" }, { "\"\\", @"""\""\\""" }, { 256, "256" }, { (ulong)long.MaxValue, long.MaxValue.ToString() }, { 45.78m, "45.78" }, { .00000457823432, "4.57823432E-06" }, { (byte)24, "24" }, { false, "false" }, { AttributeTargets.Assembly | AttributeTargets.Constructor, "33" }, { ConsoleColor.DarkCyan, "3" }, { new DateTimeOffset(1999, 5, 27, 4, 34, 45, TimeSpan.Zero), "\"1999-05-27T04:34:45+00:00\"" }, { new TimeSpan(5, 30, 0), "\"05:30:00\"" }, { new Uri("http://www.bing.com"), @"""http://www.bing.com/""" }, { new Guid("4ed1cd44-11d7-4b27-b623-0b8b553c8906"), "\"4ed1cd44-11d7-4b27-b623-0b8b553c8906\"" }, // Structs { new Point() { x = 45, Y = -5}, "{\"x\":45,\"Y\":-5}" }, // Arrays { new object[] {}, "[]" }, { new int[] { 1, 2, 3}, "[1,2,3]" }, { new string[] { "a", "b"}, "[\"a\",\"b\"]" }, { new Point[] { new Point() { x = 10, Y = 10}, new Point() { x = 20, Y = 20}}, "[{\"x\":10,\"Y\":10},{\"x\":20,\"Y\":20}]" }, // Collections { new List { 1, 2, 3}, "[1,2,3]" }, { new List { "a", "b"}, "[\"a\",\"b\"]" }, { new List { new Point() { x = 10, Y = 10}, new Point() { x = 20, Y = 20}}, "[{\"x\":10,\"Y\":10},{\"x\":20,\"Y\":20}]" }, { new MyList { 1, 2, 3}, "[1,2,3]" }, { new MyList { "a", "b"}, "[\"a\",\"b\"]" }, { new MyList { new Point() { x = 10, Y = 10}, new Point() { x = 20, Y = 20}}, "[{\"x\":10,\"Y\":10},{\"x\":20,\"Y\":20}]" }, // Dictionaries { new Dictionary { { "k1", "v1" }, { "k2", "v2" } }, "{\"k1\":\"v1\",\"k2\":\"v2\"}" }, { new Dictionary { { 1, "v1" }, { 2, "v2" } }, "{\"1\":\"v1\",\"2\":\"v2\"}" }, // Anonymous types { new { Anon1 = 56, Anon2 = "foo"}, "{\"Anon1\":56,\"Anon2\":\"foo\"}" }, // Classes { new DataContractType() { s = "foo", i = 49, NotAMember = "Error" }, "{\"s\":\"foo\",\"i\":49}" }, { new POCOType() { s = "foo", t = "Error"}, "{\"s\":\"foo\"}" }, { new SerializableType("protected") { publicField = "public", protectedInternalField = "protected internal", internalField = "internal", PublicProperty = "private", nonSerializedField = "Error" }, "{\"publicField\":\"public\",\"internalField\":\"internal\",\"protectedInternalField\":\"protected internal\",\"protectedField\":\"protected\",\"privateField\":\"private\"}" }, // Generics { new KeyValuePair("foo", false), "{\"Key\":\"foo\",\"Value\":false}" }, // ISerializable types { new ArgumentNullException("param"), "{\"ClassName\":\"System.ArgumentNullException\",\"Message\":\"Value cannot be null.\",\"Data\":null,\"InnerException\":null,\"HelpURL\":null,\"StackTraceString\":null,\"RemoteStackTraceString\":null,\"RemoteStackIndex\":0,\"ExceptionMethod\":null,\"HResult\":-2147467261,\"Source\":null,\"WatsonBuckets\":null,\"ParamName\":\"param\"}" }, // JSON Values { new JValue(false), "false" }, { new JValue(54), "54" }, { new JValue("s"), "\"s\"" }, { new JArray() { new JValue(1), new JValue(2) }, "[1,2]" }, { new JObject() { { "k1", new JValue("v1") }, { "k2", new JValue("v2") } }, "{\"k1\":\"v1\",\"k2\":\"v2\"}" }, { new KeyValuePair(new JValue("k"), new JArray() { new JValue("v1"), new JValue("v2") }), "{\"Key\":\"k\",\"Value\":[\"v1\",\"v2\"]}" }, }; } } public static IEnumerable TypedSerializedJson { get { return new TheoryDataSet() { // Null { null, "null", typeof(POCOType) }, { null, "null", typeof(JToken) }, // Nullables { new int?(), "null", typeof(int?) }, { new Point?(), "null", typeof(Point?) }, { new ConsoleColor?(), "null", typeof(ConsoleColor?) }, { new int?(45), "45", typeof(int?) }, { new Point?(new Point() { x = 45, Y = -5 }), "{\"x\":45,\"Y\":-5}", typeof(Point?) }, { new ConsoleColor?(ConsoleColor.DarkMagenta), "5", typeof(ConsoleColor?)}, }; } } [Theory] [PropertyData("SerializedJson")] public void ObjectsSerializeToExpectedJson(object o, string expectedJson) { ObjectsSerializeToExpectedJsonWithProvidedType(o, expectedJson, o.GetType()); } [Theory] [PropertyData("SerializedJson")] public void JsonDeserializesToExpectedObject(object expectedObject, string json) { JsonDeserializesToExpectedObjectWithProvidedType(expectedObject, json, expectedObject.GetType()); } [Theory] [PropertyData("TypedSerializedJson")] public void ObjectsSerializeToExpectedJsonWithProvidedType(object o, string expectedJson, Type type) { Assert.Equal(expectedJson, Serialize(o, type)); } [Theory] [PropertyData("TypedSerializedJson")] public void JsonDeserializesToExpectedObjectWithProvidedType(object expectedObject, string json, Type type) { if (expectedObject == null) { Assert.Null(Deserialize(json, type)); } else { Assert.Equal(expectedObject, Deserialize(json, type), new ObjectComparer()); } } [Fact] public void CallbacksGetCalled() { TypeWithCallbacks o = new TypeWithCallbacks(); string json = Serialize(o, typeof(TypeWithCallbacks)); Assert.Equal("12", o.callbackOrder); TypeWithCallbacks deserializedObject = Deserialize(json, typeof(TypeWithCallbacks)) as TypeWithCallbacks; Assert.Equal("34", deserializedObject.callbackOrder); } [Fact] public void DerivedTypesArePreserved() { JsonMediaTypeFormatter formatter = new JsonMediaTypeFormatter(); formatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects; string json = Serialize(new Derived(), typeof(Base), formatter); object deserializedObject = Deserialize(json, typeof(Base), formatter); Assert.IsType(typeof(Derived), deserializedObject); } [Fact] public void ArbitraryTypesArentDeserializedByDefault() { string json = "{\"$type\":\"" + typeof(DangerousType).AssemblyQualifiedName + "\"}"; object deserializedObject = Deserialize(json, typeof(object)); Assert.IsNotType(typeof(DangerousType), deserializedObject); } [Fact] public void ReferencesArePreserved() { Ref ref1 = new Ref(); Ref ref2 = new Ref(); ref1.Reference = ref2; ref2.Reference = ref1; string json = Serialize(ref1, typeof(Ref)); Ref deserializedObject = Deserialize(json, typeof(Ref)) as Ref; Assert.ReferenceEquals(deserializedObject, deserializedObject.Reference.Reference); } [Fact] public void DeserializingDeepArraysThrows() { StringBuilder sb = new StringBuilder(); int depth = 500; for (int i = 0; i < depth; i++) { sb.Append("["); } sb.Append("null"); for (int i = 0; i < depth; i++) { sb.Append("]"); } string json = sb.ToString(); Assert.Throws(typeof(JsonReaderQuotaException), () => Deserialize(json, typeof(object))); } [Theory] // existing good surrogate pair [InlineData("ABC \\ud800\\udc00 DEF", "ABC \ud800\udc00 DEF")] // invalid surrogates (two high back-to-back) [InlineData("ABC \\ud800\\ud800 DEF", "ABC \ufffd\ufffd DEF")] // invalid high surrogate at end of string [InlineData("ABC \\ud800", "ABC \ufffd")] // high surrogate not followed by low surrogate [InlineData("ABC \\ud800 DEF", "ABC \ufffd DEF")] // low surrogate not preceded by high surrogate [InlineData("ABC \\udc00\\ud800 DEF", "ABC \ufffd\ufffd DEF")] // make sure unencoded invalid surrogate characters don't make it through [InlineData("\udc00\ud800\ud800", "??????")] public void InvalidUnicodeStringsAreFixedUp(string input, string expectedString) { string json = "\"" + input + "\""; string deserializedString = Deserialize(json, typeof(string)) as string; Assert.Equal(expectedString, deserializedString); } private static string Serialize(object o, Type type, MediaTypeFormatter formatter = null) { formatter = formatter ?? new JsonMediaTypeFormatter(); MemoryStream ms = new MemoryStream(); formatter.WriteToStreamAsync(type, o, ms, null, null).Wait(); ms.Flush(); ms.Position = 0; return new StreamReader(ms).ReadToEnd(); } internal static object Deserialize(string json, Type type, MediaTypeFormatter formatter = null, IFormatterLogger formatterLogger = null) { formatter = formatter ?? new JsonMediaTypeFormatter(); MemoryStream ms = new MemoryStream(); byte[] bytes = Encoding.Default.GetBytes(json); ms.Write(bytes, 0, bytes.Length); ms.Flush(); ms.Position = 0; Task readTask = formatter.ReadFromStreamAsync(type, ms, contentHeaders: null, formatterLogger: formatterLogger); readTask.WaitUntilCompleted(); if (readTask.IsFaulted) { throw readTask.Exception.GetBaseException(); } return readTask.Result; } } public class ObjectComparer : IEqualityComparer { bool IEqualityComparer.Equals(object x, object y) { Type xType = x.GetType(); if (xType == y.GetType()) { if (typeof(JToken).IsAssignableFrom(xType) || xType == typeof(ArgumentNullException) || xType == typeof(KeyValuePair)) { return x.ToString() == y.ToString(); } if (xType == typeof(DataContractType)) { return Equals(x, y); } if (xType == typeof(POCOType)) { return Equals(x, y); } if (xType == typeof(SerializableType)) { return Equals(x, y); } if (xType == typeof(Point)) { return Equals(x, y); } if (typeof(IEnumerable).IsAssignableFrom(xType)) { IEnumerator xEnumerator = ((IEnumerable)x).GetEnumerator(); IEnumerator yEnumerator = ((IEnumerable)y).GetEnumerator(); while (xEnumerator.MoveNext()) { // if x is longer than y if (!yEnumerator.MoveNext()) { return false; } else { if (!xEnumerator.Current.Equals(yEnumerator.Current)) { return false; } } } // if y is longer than x if (yEnumerator.MoveNext()) { return false; } return true; } } return x.Equals(y); } int IEqualityComparer.GetHashCode(object obj) { throw new NotImplementedException(); } bool Equals(object x, object y) where T : IEquatable { IEquatable yEquatable = (IEquatable)y; return yEquatable.Equals((T)x); } } // Marked as [Serializable] to check that [DataContract] takes precedence over [Serializable] [DataContract] [Serializable] public class DataContractType : IEquatable { [DataMember] public string s; [DataMember] internal int i; public string NotAMember; public bool Equals(DataContractType other) { return this.s == other.s && this.i == other.i; } } public class POCOType : IEquatable { public string s; internal string t; public bool Equals(POCOType other) { return this.s == other.s; } } public class MyList : ICollection { List innerList = new List(); public IEnumerator GetEnumerator() { return innerList.GetEnumerator(); } public void Add(T item) { innerList.Add(item); } public void Clear() { innerList.Clear(); } public bool Contains(T item) { return innerList.Contains(item); } public void CopyTo(T[] array, int arrayIndex) { innerList.CopyTo(array, arrayIndex); } public int Count { get { return innerList.Count; } } public bool IsReadOnly { get { return ((ICollection)innerList).IsReadOnly; } } public bool Remove(T item) { return innerList.Remove(item); } IEnumerator IEnumerable.GetEnumerator() { return innerList.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return innerList.GetEnumerator(); } } [Serializable] class SerializableType : IEquatable { public SerializableType(string protectedFieldValue) { this.protectedField = protectedFieldValue; } public string publicField; internal string internalField; protected internal string protectedInternalField; protected string protectedField; private string privateField; public string PublicProperty { get { return privateField; } set { this.privateField = value; } } [NonSerialized] public string nonSerializedField; public bool Equals(SerializableType other) { return this.publicField == other.publicField && this.internalField == other.internalField && this.protectedInternalField == other.protectedInternalField && this.protectedField == other.protectedField && this.privateField == other.privateField; } } public struct Point : IEquatable { public int x; public int Y { get; set; } public bool Equals(Point other) { return this.x == other.x && this.Y == other.Y; } } [DataContract(IsReference = true)] public class Ref { [DataMember] public Ref Reference; } public class Base { } public class Derived : Base { } [DataContract] public class TypeWithCallbacks { public string callbackOrder = ""; [OnSerializing] public void OnSerializing(StreamingContext c) { callbackOrder += "1"; } [OnSerialized] public void OnSerialized(StreamingContext c) { callbackOrder += "2"; } [OnDeserializing] public void OnDeserializing(StreamingContext c) { callbackOrder += "3"; } [OnDeserialized] public void OnDeserialized(StreamingContext c) { callbackOrder += "4"; } } public class DangerousType { } }