Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

519 lines
18 KiB
C#

// 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<object[]> SerializedJson
{
get
{
return new TheoryDataSet<object, string>()
{
// 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<int> { 1, 2, 3}, "[1,2,3]" },
{ new List<string> { "a", "b"}, "[\"a\",\"b\"]" },
{ new List<Point> { new Point() { x = 10, Y = 10}, new Point() { x = 20, Y = 20}}, "[{\"x\":10,\"Y\":10},{\"x\":20,\"Y\":20}]" },
{ new MyList<int> { 1, 2, 3}, "[1,2,3]" },
{ new MyList<string> { "a", "b"}, "[\"a\",\"b\"]" },
{ new MyList<Point> { new Point() { x = 10, Y = 10}, new Point() { x = 20, Y = 20}}, "[{\"x\":10,\"Y\":10},{\"x\":20,\"Y\":20}]" },
// Dictionaries
{ new Dictionary<string, string> { { "k1", "v1" }, { "k2", "v2" } }, "{\"k1\":\"v1\",\"k2\":\"v2\"}" },
{ new Dictionary<int, string> { { 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<string, bool>("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<JToken, JToken>(new JValue("k"), new JArray() { new JValue("v1"), new JValue("v2") }), "{\"Key\":\"k\",\"Value\":[\"v1\",\"v2\"]}" },
};
}
}
public static IEnumerable<object[]> TypedSerializedJson
{
get
{
return new TheoryDataSet<object, string, Type>()
{
// 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<object> 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<object>
{
bool IEqualityComparer<object>.Equals(object x, object y)
{
Type xType = x.GetType();
if (xType == y.GetType())
{
if (typeof(JToken).IsAssignableFrom(xType) || xType == typeof(ArgumentNullException) || xType == typeof(KeyValuePair<JToken, JToken>))
{
return x.ToString() == y.ToString();
}
if (xType == typeof(DataContractType))
{
return Equals<DataContractType>(x, y);
}
if (xType == typeof(POCOType))
{
return Equals<POCOType>(x, y);
}
if (xType == typeof(SerializableType))
{
return Equals<SerializableType>(x, y);
}
if (xType == typeof(Point))
{
return Equals<Point>(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<object>.GetHashCode(object obj)
{
throw new NotImplementedException();
}
bool Equals<T>(object x, object y) where T : IEquatable<T>
{
IEquatable<T> yEquatable = (IEquatable<T>)y;
return yEquatable.Equals((T)x);
}
}
// Marked as [Serializable] to check that [DataContract] takes precedence over [Serializable]
[DataContract]
[Serializable]
public class DataContractType : IEquatable<DataContractType>
{
[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<POCOType>
{
public string s;
internal string t;
public bool Equals(POCOType other)
{
return this.s == other.s;
}
}
public class MyList<T> : ICollection<T>
{
List<T> innerList = new List<T>();
public IEnumerator<T> 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<T>)innerList).IsReadOnly; }
}
public bool Remove(T item)
{
return innerList.Remove(item);
}
IEnumerator IEnumerable.GetEnumerator()
{
return innerList.GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return innerList.GetEnumerator();
}
}
[Serializable]
class SerializableType : IEquatable<SerializableType>
{
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<Point>
{
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
{
}
}