diff --git a/Src/Newtonsoft.Json.Tests/Serialization/EntitiesSerializationTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/EntitiesSerializationTests.cs index ef9ae25d..168d0c0c 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/EntitiesSerializationTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/EntitiesSerializationTests.cs @@ -23,6 +23,7 @@ // OTHER DEALINGS IN THE SOFTWARE. #endregion +using Newtonsoft.Json.Serialization; #if !(NET35 || NET20 || NETFX_CORE || PORTABLE || PORTABLE40) using System; using System.Collections.Generic; @@ -125,6 +126,96 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.AreEqual(expected, json); } + [Test] + public void SerializeEntityCamelCase() + { + Folder rootFolder = CreateEntitiesTestData(); + + JsonSerializerSettings settings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + ContractResolver = new CamelCasePropertyNamesContractResolver(), + Converters = { new IsoDateTimeConverter() } + }; + + string json = JsonConvert.SerializeObject(rootFolder, Formatting.None, settings); + + Console.WriteLine(json); + + string expected = @"{ + ""$id"": ""1"", + ""folderId"": ""a4e8ba80-eb24-4591-bb1c-62d3ad83701e"", + ""name"": ""Root folder"", + ""description"": ""Description!"", + ""createdDate"": ""2000-12-10T10:50:00Z"", + ""files"": [], + ""childFolders"": [ + { + ""$id"": ""2"", + ""folderId"": ""484936e2-7cbb-4592-93ff-b2103e5705e4"", + ""name"": ""Child folder"", + ""description"": ""Description!"", + ""createdDate"": ""2001-11-20T10:50:00Z"", + ""files"": [ + { + ""$id"": ""3"", + ""fileId"": ""cc76d734-49f1-4616-bb38-41514228ac6c"", + ""name"": ""File 1"", + ""description"": ""Description!"", + ""createdDate"": ""2002-10-30T10:50:00Z"", + ""folder"": { + ""$ref"": ""2"" + }, + ""entityKey"": { + ""$id"": ""4"", + ""entitySetName"": ""File"", + ""entityContainerName"": ""DataServicesTestDatabaseEntities"", + ""entityKeyValues"": [ + { + ""key"": ""FileId"", + ""type"": ""System.Guid"", + ""value"": ""cc76d734-49f1-4616-bb38-41514228ac6c"" + } + ] + } + } + ], + ""childFolders"": [], + ""parentFolder"": { + ""$ref"": ""1"" + }, + ""entityKey"": { + ""$id"": ""5"", + ""entitySetName"": ""Folder"", + ""entityContainerName"": ""DataServicesTestDatabaseEntities"", + ""entityKeyValues"": [ + { + ""key"": ""FolderId"", + ""type"": ""System.Guid"", + ""value"": ""484936e2-7cbb-4592-93ff-b2103e5705e4"" + } + ] + } + } + ], + ""parentFolder"": null, + ""entityKey"": { + ""$id"": ""6"", + ""entitySetName"": ""Folder"", + ""entityContainerName"": ""DataServicesTestDatabaseEntities"", + ""entityKeyValues"": [ + { + ""key"": ""FolderId"", + ""type"": ""System.Guid"", + ""value"": ""a4e8ba80-eb24-4591-bb1c-62d3ad83701e"" + } + ] + } +}"; + + Assert.AreEqual(expected, json); + } + [Test] public void DeserializeEntity() { @@ -227,6 +318,76 @@ namespace Newtonsoft.Json.Tests.Serialization [Test] public void SerializeMultiValueEntityKey() + { + EntityKey e = new EntityKey("DataServicesTestDatabaseEntities.Folder", + new List + { + new EntityKeyMember("GuidId", new Guid("A4E8BA80-EB24-4591-BB1C-62D3AD83701E")), + new EntityKeyMember("IntId", int.MaxValue), + new EntityKeyMember("LongId", long.MaxValue), + new EntityKeyMember("StringId", "String!"), + new EntityKeyMember("DateTimeId", new DateTime(2000, 12, 10, 10, 50, 0, DateTimeKind.Utc)) + }); + + JsonSerializerSettings settings = new JsonSerializerSettings + { + Formatting = Formatting.Indented, + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + string json = JsonConvert.SerializeObject(e, settings); + + Assert.AreEqual(@"{ + ""$id"": ""1"", + ""entitySetName"": ""Folder"", + ""entityContainerName"": ""DataServicesTestDatabaseEntities"", + ""entityKeyValues"": [ + { + ""key"": ""GuidId"", + ""type"": ""System.Guid"", + ""value"": ""a4e8ba80-eb24-4591-bb1c-62d3ad83701e"" + }, + { + ""key"": ""IntId"", + ""type"": ""System.Int32"", + ""value"": ""2147483647"" + }, + { + ""key"": ""LongId"", + ""type"": ""System.Int64"", + ""value"": ""9223372036854775807"" + }, + { + ""key"": ""StringId"", + ""type"": ""System.String"", + ""value"": ""String!"" + }, + { + ""key"": ""DateTimeId"", + ""type"": ""System.DateTime"", + ""value"": ""12/10/2000 10:50:00"" + } + ] +}", json); + + EntityKey newKey = JsonConvert.DeserializeObject(json); + Assert.IsFalse(ReferenceEquals(e, newKey)); + + Assert.AreEqual(5, newKey.EntityKeyValues.Length); + Assert.AreEqual("GuidId", newKey.EntityKeyValues[0].Key); + Assert.AreEqual(new Guid("A4E8BA80-EB24-4591-BB1C-62D3AD83701E"), newKey.EntityKeyValues[0].Value); + Assert.AreEqual("IntId", newKey.EntityKeyValues[1].Key); + Assert.AreEqual(int.MaxValue, newKey.EntityKeyValues[1].Value); + Assert.AreEqual("LongId", newKey.EntityKeyValues[2].Key); + Assert.AreEqual(long.MaxValue, newKey.EntityKeyValues[2].Value); + Assert.AreEqual("StringId", newKey.EntityKeyValues[3].Key); + Assert.AreEqual("String!", newKey.EntityKeyValues[3].Value); + Assert.AreEqual("DateTimeId", newKey.EntityKeyValues[4].Key); + Assert.AreEqual(new DateTime(2000, 12, 10, 10, 50, 0, DateTimeKind.Utc), newKey.EntityKeyValues[4].Value); + } + + [Test] + public void SerializeMultiValueEntityKeyCameCase() { EntityKey e = new EntityKey("DataServicesTestDatabaseEntities.Folder", new List diff --git a/Src/Newtonsoft.Json/Converters/DiscriminatedUnionConverter.cs b/Src/Newtonsoft.Json/Converters/DiscriminatedUnionConverter.cs new file mode 100644 index 00000000..2f31b8de --- /dev/null +++ b/Src/Newtonsoft.Json/Converters/DiscriminatedUnionConverter.cs @@ -0,0 +1,215 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data.Odbc; +using System.Reflection; +using Newtonsoft.Json.Serialization; +using System.Globalization; +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Converters +{ + /// + /// Converts a F# discriminated union type to and from JSON. + /// + public class DiscriminatedUnionConverter : JsonConverter + { + private static bool _initialized; + private static MethodCall _isUnion; + private static MethodCall _getUnionFields; + private static MethodCall _getUnionCases; + private static MethodCall _makeUnion; + private static Func _getUnionCaseInfoName; + private static Func _getUnionCaseInfo; + private static Func _getUnionCaseFields; + private static MethodCall _getUnionCaseInfoFields; + + private const string CasePropertyName = "Case"; + private const string FieldsPropertyName = "Fields"; + + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + DefaultContractResolver resolver = serializer.ContractResolver as DefaultContractResolver; + + Type t = value.GetType(); + + object result = _getUnionFields(null, value, t, null); + object info = _getUnionCaseInfo(result); + object fields = _getUnionCaseFields(result); + object caseName = _getUnionCaseInfoName(info); + + writer.WriteStartObject(); + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(CasePropertyName) : CasePropertyName); + writer.WriteValue((string)caseName); + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(FieldsPropertyName) : FieldsPropertyName); + serializer.Serialize(writer, fields); + writer.WriteEndObject(); + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing value of object being read. + /// The calling serializer. + /// The object value. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + return null; + + IEnumerable cases = (IEnumerable)_getUnionCases(null, objectType, null); + + ReadAndAssertProperty(reader, CasePropertyName); + ReadAndAssert(reader); + + string caseName = reader.Value.ToString(); + + object matchingCaseInfo = null; + foreach (object c in cases) + { + if ((string)_getUnionCaseInfoName(c) == caseName) + { + matchingCaseInfo = c; + break; + } + } + + if (matchingCaseInfo == null) + throw new JsonSerializationException("No union type found with the name '{0}'.".FormatWith(CultureInfo.InvariantCulture, caseName)); + + ReadAndAssertProperty(reader, FieldsPropertyName); + // start array + ReadAndAssert(reader); + // first value + ReadAndAssert(reader); + + PropertyInfo[] fieldProperties = (PropertyInfo[])_getUnionCaseInfoFields(matchingCaseInfo); + List fieldValues = new List(); + foreach (PropertyInfo field in fieldProperties) + { + fieldValues.Add(serializer.Deserialize(reader, field.PropertyType)); + ReadAndAssert(reader); + } + + // end object + ReadAndAssert(reader); + + return _makeUnion(null, matchingCaseInfo, fieldValues.ToArray(), null); + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// + /// true if this instance can convert the specified object type; otherwise, false. + /// + public override bool CanConvert(Type objectType) + { + if (typeof(IEnumerable).IsAssignableFrom(objectType)) + return false; + + // all fsharp objects have CompilationMappingAttribute + // get the fsharp assembly from the attribute and initialize latebound methods + object[] attributes = objectType.GetCustomAttributes(true); + bool isFSharpType = false; + foreach (object attribute in attributes) + { + Type attributeType = attribute.GetType(); + if (attributeType.Name == "CompilationMappingAttribute") + { + EnsureInitialized(attributeType); + + isFSharpType = true; + break; + } + } + + if (!isFSharpType) + return false; + + return (bool)_isUnion(null, objectType, null); + } + + private static void EnsureInitialized(Type attributeType) + { + if (!_initialized) + { + _initialized = true; + + Assembly fsharpCoreAssembly = attributeType.Assembly; + Type fsharpType = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.FSharpType"); + + MethodInfo isUnionMethodInfo = fsharpType.GetMethod("IsUnion", BindingFlags.Public | BindingFlags.Static); + _isUnion = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(isUnionMethodInfo); + + MethodInfo getUnionCasesMethodInfo = fsharpType.GetMethod("GetUnionCases", BindingFlags.Public | BindingFlags.Static); + _getUnionCases = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(getUnionCasesMethodInfo); + + Type fsharpValue = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.FSharpValue"); + + MethodInfo getUnionFieldsMethodInfo = fsharpValue.GetMethod("GetUnionFields", BindingFlags.Public | BindingFlags.Static); + _getUnionFields = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(getUnionFieldsMethodInfo); + + _getUnionCaseInfo = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(getUnionFieldsMethodInfo.ReturnType.GetProperty("Item1")); + _getUnionCaseFields = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(getUnionFieldsMethodInfo.ReturnType.GetProperty("Item2")); + + MethodInfo makeUnionMethodInfo = fsharpValue.GetMethod("MakeUnion", BindingFlags.Public | BindingFlags.Static); + _makeUnion = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(makeUnionMethodInfo); + + Type unionCaseInfo = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.UnionCaseInfo"); + + _getUnionCaseInfoName = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(unionCaseInfo.GetProperty("Name")); + _getUnionCaseInfoFields = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(unionCaseInfo.GetMethod("GetFields")); + } + } + + private static void ReadAndAssertProperty(JsonReader reader, string propertyName) + { + ReadAndAssert(reader); + + if (reader.TokenType != JsonToken.PropertyName || !string.Equals(reader.Value.ToString(), propertyName, StringComparison.OrdinalIgnoreCase)) + throw new JsonSerializationException("Expected JSON property '{0}'.".FormatWith(CultureInfo.InvariantCulture, propertyName)); + } + + private static void ReadAndAssert(JsonReader reader) + { + if (!reader.Read()) + throw new JsonSerializationException("Unexpected end."); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Converters/DiscriminatedUnionTypeConverter.cs b/Src/Newtonsoft.Json/Converters/DiscriminatedUnionTypeConverter.cs new file mode 100644 index 00000000..55295c44 --- /dev/null +++ b/Src/Newtonsoft.Json/Converters/DiscriminatedUnionTypeConverter.cs @@ -0,0 +1,215 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data.Odbc; +using System.Reflection; +using Newtonsoft.Json.Serialization; +using System.Globalization; +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Converters +{ + /// + /// Converts a F# union type to and from JSON. + /// + public class DiscriminatedUnionTypeConverter : JsonConverter + { + private static bool _initialized; + private static MethodCall _isUnion; + private static MethodCall _getUnionFields; + private static MethodCall _getUnionCases; + private static MethodCall _makeUnion; + private static Func _getUnionCaseInfoName; + private static Func _getUnionCaseInfo; + private static Func _getUnionCaseFields; + private static MethodCall _getUnionCaseInfoFields; + + private const string CasePropertyName = "Case"; + private const string FieldsPropertyName = "Fields"; + + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + DefaultContractResolver resolver = serializer.ContractResolver as DefaultContractResolver; + + Type t = value.GetType(); + + object result = _getUnionFields(null, value, t, null); + object info = _getUnionCaseInfo(result); + object fields = _getUnionCaseFields(result); + object caseName = _getUnionCaseInfoName(info); + + writer.WriteStartObject(); + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(CasePropertyName) : CasePropertyName); + writer.WriteValue((string)caseName); + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(FieldsPropertyName) : FieldsPropertyName); + serializer.Serialize(writer, fields); + writer.WriteEndObject(); + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing value of object being read. + /// The calling serializer. + /// The object value. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + return null; + + IEnumerable cases = (IEnumerable)_getUnionCases(null, objectType, null); + + ReadAndAssertProperty(reader, CasePropertyName); + ReadAndAssert(reader); + + string caseName = reader.Value.ToString(); + + object matchingCaseInfo = null; + foreach (object c in cases) + { + if ((string)_getUnionCaseInfoName(c) == caseName) + { + matchingCaseInfo = c; + break; + } + } + + if (matchingCaseInfo == null) + throw new JsonSerializationException("No union type found with the name '{0}'.".FormatWith(CultureInfo.InvariantCulture, caseName)); + + ReadAndAssertProperty(reader, FieldsPropertyName); + // start array + ReadAndAssert(reader); + // first value + ReadAndAssert(reader); + + PropertyInfo[] fieldProperties = (PropertyInfo[])_getUnionCaseInfoFields(matchingCaseInfo); + List fieldValues = new List(); + foreach (PropertyInfo field in fieldProperties) + { + fieldValues.Add(serializer.Deserialize(reader, field.PropertyType)); + ReadAndAssert(reader); + } + + // end object + ReadAndAssert(reader); + + return _makeUnion(null, matchingCaseInfo, fieldValues.ToArray(), null); + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// + /// true if this instance can convert the specified object type; otherwise, false. + /// + public override bool CanConvert(Type objectType) + { + if (typeof(IEnumerable).IsAssignableFrom(objectType)) + return false; + + // all fsharp objects have CompilationMappingAttribute + // get the fsharp assembly from the attribute and initialize latebound methods + object[] attributes = objectType.GetCustomAttributes(true); + bool isFSharpType = false; + foreach (object attribute in attributes) + { + Type attributeType = attribute.GetType(); + if (attributeType.Name == "CompilationMappingAttribute") + { + EnsureInitialized(attributeType); + + isFSharpType = true; + break; + } + } + + if (!isFSharpType) + return false; + + return (bool)_isUnion(null, objectType, null); + } + + private static void EnsureInitialized(Type attributeType) + { + if (!_initialized) + { + _initialized = true; + + Assembly fsharpCoreAssembly = attributeType.Assembly; + Type fsharpType = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.FSharpType"); + + MethodInfo isUnionMethodInfo = fsharpType.GetMethod("IsUnion", BindingFlags.Public | BindingFlags.Static); + _isUnion = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(isUnionMethodInfo); + + MethodInfo getUnionCasesMethodInfo = fsharpType.GetMethod("GetUnionCases", BindingFlags.Public | BindingFlags.Static); + _getUnionCases = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(getUnionCasesMethodInfo); + + Type fsharpValue = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.FSharpValue"); + + MethodInfo getUnionFieldsMethodInfo = fsharpValue.GetMethod("GetUnionFields", BindingFlags.Public | BindingFlags.Static); + _getUnionFields = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(getUnionFieldsMethodInfo); + + _getUnionCaseInfo = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(getUnionFieldsMethodInfo.ReturnType.GetProperty("Item1")); + _getUnionCaseFields = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(getUnionFieldsMethodInfo.ReturnType.GetProperty("Item2")); + + MethodInfo makeUnionMethodInfo = fsharpValue.GetMethod("MakeUnion", BindingFlags.Public | BindingFlags.Static); + _makeUnion = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(makeUnionMethodInfo); + + Type unionCaseInfo = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.UnionCaseInfo"); + + _getUnionCaseInfoName = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(unionCaseInfo.GetProperty("Name")); + _getUnionCaseInfoFields = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(unionCaseInfo.GetMethod("GetFields")); + } + } + + private static void ReadAndAssertProperty(JsonReader reader, string propertyName) + { + ReadAndAssert(reader); + + if (reader.TokenType != JsonToken.PropertyName || !string.Equals(reader.Value.ToString(), propertyName, StringComparison.OrdinalIgnoreCase)) + throw new JsonSerializationException("Expected JSON property '{0}'.".FormatWith(CultureInfo.InvariantCulture, propertyName)); + } + + private static void ReadAndAssert(JsonReader reader) + { + if (!reader.Read()) + throw new JsonSerializationException("Unexpected end."); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Converters/EntityKeyMemberConverter.cs b/Src/Newtonsoft.Json/Converters/EntityKeyMemberConverter.cs index 26f654bd..e7ad4f64 100644 --- a/Src/Newtonsoft.Json/Converters/EntityKeyMemberConverter.cs +++ b/Src/Newtonsoft.Json/Converters/EntityKeyMemberConverter.cs @@ -44,6 +44,10 @@ namespace Newtonsoft.Json.Converters { private const string EntityKeyMemberFullTypeName = "System.Data.EntityKeyMember"; + private const string KeyPropertyName = "Key"; + private const string TypePropertyName = "Type"; + private const string ValuePropertyName = "Value"; + /// /// Writes the JSON representation of the object. /// @@ -52,16 +56,18 @@ namespace Newtonsoft.Json.Converters /// The calling serializer. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { + DefaultContractResolver resolver = serializer.ContractResolver as DefaultContractResolver; + IEntityKeyMember entityKeyMember = DynamicWrapper.CreateWrapper(value); Type keyType = (entityKeyMember.Value != null) ? entityKeyMember.Value.GetType() : null; writer.WriteStartObject(); - writer.WritePropertyName("Key"); + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(KeyPropertyName) : KeyPropertyName); writer.WriteValue(entityKeyMember.Key); - writer.WritePropertyName("Type"); + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(TypePropertyName) : TypePropertyName); writer.WriteValue((keyType != null) ? keyType.FullName : null); - writer.WritePropertyName("Value"); + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(ValuePropertyName) : ValuePropertyName); if (keyType != null) { @@ -83,7 +89,7 @@ namespace Newtonsoft.Json.Converters { ReadAndAssert(reader); - if (reader.TokenType != JsonToken.PropertyName || reader.Value.ToString() != propertyName) + if (reader.TokenType != JsonToken.PropertyName || !string.Equals(reader.Value.ToString(), propertyName, StringComparison.OrdinalIgnoreCase)) throw new JsonSerializationException("Expected JSON property '{0}'.".FormatWith(CultureInfo.InvariantCulture, propertyName)); } @@ -105,17 +111,17 @@ namespace Newtonsoft.Json.Converters { IEntityKeyMember entityKeyMember = DynamicWrapper.CreateWrapper(Activator.CreateInstance(objectType)); - ReadAndAssertProperty(reader, "Key"); + ReadAndAssertProperty(reader, KeyPropertyName); ReadAndAssert(reader); entityKeyMember.Key = reader.Value.ToString(); - ReadAndAssertProperty(reader, "Type"); + ReadAndAssertProperty(reader, TypePropertyName); ReadAndAssert(reader); string type = reader.Value.ToString(); Type t = Type.GetType(type); - ReadAndAssertProperty(reader, "Value"); + ReadAndAssertProperty(reader, ValuePropertyName); ReadAndAssert(reader); entityKeyMember.Value = serializer.Deserialize(reader, t); @@ -133,9 +139,8 @@ namespace Newtonsoft.Json.Converters /// public override bool CanConvert(Type objectType) { - return (objectType.AssignableToTypeName(EntityKeyMemberFullTypeName)); + return objectType.AssignableToTypeName(EntityKeyMemberFullTypeName); } } } - #endif \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Converters/UnionTypeConverter.cs b/Src/Newtonsoft.Json/Converters/UnionTypeConverter.cs new file mode 100644 index 00000000..2d597142 --- /dev/null +++ b/Src/Newtonsoft.Json/Converters/UnionTypeConverter.cs @@ -0,0 +1,215 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data.Odbc; +using System.Reflection; +using Newtonsoft.Json.Serialization; +using System.Globalization; +using Newtonsoft.Json.Utilities; + +namespace Newtonsoft.Json.Converters +{ + /// + /// Converts a F# union type to and from JSON. + /// + public class UnionTypeConverter : JsonConverter + { + private static bool _initialized; + private static MethodCall _isUnionMethod; + private static MethodCall _getUnionFieldsMethod; + private static MethodCall _getUnionCasesMethod; + private static MethodCall _makeUnionMethod; + private static Func _getUnionCaseInfoName; + private static Func _getUnionCaseInfo; + private static Func _getUnionCaseFields; + private static MethodCall _getUnionCaseInfoFields; + + private const string CasePropertyName = "Case"; + private const string FieldsPropertyName = "Fields"; + + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value. + /// The calling serializer. + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + DefaultContractResolver resolver = serializer.ContractResolver as DefaultContractResolver; + + Type t = value.GetType(); + + object result = _getUnionFieldsMethod(null, value, t, null); + object info = _getUnionCaseInfo(result); + object fields = _getUnionCaseFields(result); + object caseName = _getUnionCaseInfoName(info); + + writer.WriteStartObject(); + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(CasePropertyName) : CasePropertyName); + writer.WriteValue((string)caseName); + writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(FieldsPropertyName) : FieldsPropertyName); + serializer.Serialize(writer, fields); + writer.WriteEndObject(); + } + + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing value of object being read. + /// The calling serializer. + /// The object value. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) + return null; + + IEnumerable cases = (IEnumerable)_getUnionCasesMethod(null, objectType, null); + + ReadAndAssertProperty(reader, CasePropertyName); + ReadAndAssert(reader); + + string caseName = reader.Value.ToString(); + + object matchingCaseInfo = null; + foreach (object c in cases) + { + if ((string)_getUnionCaseInfoName(c) == caseName) + { + matchingCaseInfo = c; + break; + } + } + + if (matchingCaseInfo == null) + throw new JsonSerializationException("No union type found with the name '{0}'.".FormatWith(CultureInfo.InvariantCulture, caseName)); + + ReadAndAssertProperty(reader, FieldsPropertyName); + // start array + ReadAndAssert(reader); + // first value + ReadAndAssert(reader); + + PropertyInfo[] fieldProperties = (PropertyInfo[])_getUnionCaseInfoFields(matchingCaseInfo); + List fieldValues = new List(); + foreach (PropertyInfo field in fieldProperties) + { + fieldValues.Add(serializer.Deserialize(reader, field.PropertyType)); + ReadAndAssert(reader); + } + + // end object + ReadAndAssert(reader); + + return _makeUnionMethod(null, matchingCaseInfo, fieldValues.ToArray(), null); + } + + /// + /// Determines whether this instance can convert the specified object type. + /// + /// Type of the object. + /// + /// true if this instance can convert the specified object type; otherwise, false. + /// + public override bool CanConvert(Type objectType) + { + if (typeof(IEnumerable).IsAssignableFrom(objectType)) + return false; + + // all fsharp objects have CompilationMappingAttribute + // get the fsharp assembly from the attribute and initialize latebound methods + object[] attributes = objectType.GetCustomAttributes(true); + bool isFSharpType = false; + foreach (object attribute in attributes) + { + Type attributeType = attribute.GetType(); + if (attributeType.Name == "CompilationMappingAttribute") + { + EnsureInitialized(attributeType); + + isFSharpType = true; + break; + } + } + + if (!isFSharpType) + return false; + + return (bool)_isUnionMethod(null, objectType, null); + } + + private static void EnsureInitialized(Type attributeType) + { + if (!_initialized) + { + _initialized = true; + + Assembly fsharpCoreAssembly = attributeType.Assembly; + Type fsharpType = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.FSharpType"); + + MethodInfo isUnionMethodInfo = fsharpType.GetMethod("IsUnion", BindingFlags.Public | BindingFlags.Static); + _isUnionMethod = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(isUnionMethodInfo); + + MethodInfo getUnionCasesMethodInfo = fsharpType.GetMethod("GetUnionCases", BindingFlags.Public | BindingFlags.Static); + _getUnionCasesMethod = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(getUnionCasesMethodInfo); + + Type fsharpValue = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.FSharpValue"); + + MethodInfo getUnionFieldsMethodInfo = fsharpValue.GetMethod("GetUnionFields", BindingFlags.Public | BindingFlags.Static); + _getUnionFieldsMethod = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(getUnionFieldsMethodInfo); + + _getUnionCaseInfo = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(getUnionFieldsMethodInfo.ReturnType.GetProperty("Item1")); + _getUnionCaseFields = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(getUnionFieldsMethodInfo.ReturnType.GetProperty("Item2")); + + MethodInfo makeUnionMethodInfo = fsharpValue.GetMethod("MakeUnion", BindingFlags.Public | BindingFlags.Static); + _makeUnionMethod = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(makeUnionMethodInfo); + + Type unionCaseInfo = fsharpCoreAssembly.GetType("Microsoft.FSharp.Reflection.UnionCaseInfo"); + + _getUnionCaseInfoName = JsonTypeReflector.ReflectionDelegateFactory.CreateGet(unionCaseInfo.GetProperty("Name")); + _getUnionCaseInfoFields = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(unionCaseInfo.GetMethod("GetFields")); + } + } + + private static void ReadAndAssertProperty(JsonReader reader, string propertyName) + { + ReadAndAssert(reader); + + if (reader.TokenType != JsonToken.PropertyName || !string.Equals(reader.Value.ToString(), propertyName, StringComparison.OrdinalIgnoreCase)) + throw new JsonSerializationException("Expected JSON property '{0}'.".FormatWith(CultureInfo.InvariantCulture, propertyName)); + } + + private static void ReadAndAssert(JsonReader reader) + { + if (!reader.Read()) + throw new JsonSerializationException("Unexpected end."); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs b/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs new file mode 100644 index 00000000..4ff4aabf --- /dev/null +++ b/Src/Newtonsoft.Json/Linq/JsonPath/QueryExpression.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; + +namespace Newtonsoft.Json.Linq.JsonPath +{ + internal enum QueryOperator + { + None, + Equals, + NotEquals, + Exists, + LessThan, + LessThanOrEquals, + GreaterThan, + GreaterThanOrEquals, + And, + Or + } + + internal abstract class QueryExpression + { + public QueryOperator Operator { get; set; } + + public abstract bool IsMatch(JToken t); + } + + internal class CompositeExpression : QueryExpression + { + public List Expressions { get; set; } + + public CompositeExpression() + { + Expressions = new List(); + } + + public override bool IsMatch(JToken t) + { + switch (Operator) + { + case QueryOperator.And: + foreach (QueryExpression e in Expressions) + { + if (!e.IsMatch(t)) + return false; + } + return true; + case QueryOperator.Or: + foreach (QueryExpression e in Expressions) + { + if (e.IsMatch(t)) + return true; + } + return false; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + internal class BooleanQueryExpression : QueryExpression + { + public List Path { get; set; } + public JValue Value { get; set; } + + public override bool IsMatch(JToken t) + { + IEnumerable pathResult = JPath.Evaluate(Path, t, false); + + foreach (JToken r in pathResult) + { + JValue v = r as JValue; + switch (Operator) + { + case QueryOperator.Equals: + if (v != null && v.Equals(Value)) + return true; + break; + case QueryOperator.NotEquals: + if (v != null && !v.Equals(Value)) + return true; + break; + case QueryOperator.GreaterThan: + if (v != null && v.CompareTo(Value) > 0) + return true; + break; + case QueryOperator.GreaterThanOrEquals: + if (v != null && v.CompareTo(Value) >= 0) + return true; + break; + case QueryOperator.LessThan: + if (v != null && v.CompareTo(Value) < 0) + return true; + break; + case QueryOperator.LessThanOrEquals: + if (v != null && v.CompareTo(Value) <= 0) + return true; + break; + case QueryOperator.Exists: + return true; + default: + throw new ArgumentOutOfRangeException(); + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs b/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs index 106ca42b..b564a06e 100644 --- a/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs +++ b/Src/Newtonsoft.Json/Linq/JsonPath/QueryFilter.cs @@ -4,109 +4,6 @@ using System.Linq; namespace Newtonsoft.Json.Linq.JsonPath { - internal enum QueryOperator - { - None, - Equals, - NotEquals, - Exists, - LessThan, - LessThanOrEquals, - GreaterThan, - GreaterThanOrEquals, - And, - Or - } - - internal abstract class QueryExpression - { - public QueryOperator Operator { get; set; } - - public abstract bool IsMatch(JToken t); - } - - internal class CompositeExpression : QueryExpression - { - public List Expressions { get; set; } - - public CompositeExpression() - { - Expressions = new List(); - } - - public override bool IsMatch(JToken t) - { - switch (Operator) - { - case QueryOperator.And: - foreach (QueryExpression e in Expressions) - { - if (!e.IsMatch(t)) - return false; - } - return true; - case QueryOperator.Or: - foreach (QueryExpression e in Expressions) - { - if (e.IsMatch(t)) - return true; - } - return false; - default: - throw new ArgumentOutOfRangeException(); - } - } - } - - internal class BooleanQueryExpression : QueryExpression - { - public List Path { get; set; } - public JValue Value { get; set; } - - public override bool IsMatch(JToken t) - { - IEnumerable pathResult = JPath.Evaluate(Path, t, false); - - foreach (JToken r in pathResult) - { - JValue v = r as JValue; - switch (Operator) - { - case QueryOperator.Equals: - if (v != null && v.Equals(Value)) - return true; - break; - case QueryOperator.NotEquals: - if (v != null && !v.Equals(Value)) - return true; - break; - case QueryOperator.GreaterThan: - if (v != null && v.CompareTo(Value) > 0) - return true; - break; - case QueryOperator.GreaterThanOrEquals: - if (v != null && v.CompareTo(Value) >= 0) - return true; - break; - case QueryOperator.LessThan: - if (v != null && v.CompareTo(Value) < 0) - return true; - break; - case QueryOperator.LessThanOrEquals: - if (v != null && v.CompareTo(Value) <= 0) - return true; - break; - case QueryOperator.Exists: - return true; - default: - throw new ArgumentOutOfRangeException(); - } - } - - return false; - } - } - internal class QueryFilter : PathFilter { public QueryExpression Expression { get; set; } diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index f08cb031..116396dd 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -60,6 +60,7 @@ + @@ -121,6 +122,7 @@ + diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index 709f6392..b5fac9ed 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -103,7 +103,7 @@ namespace Newtonsoft.Json.Serialization #if !(NET35 || NET20 || PORTABLE40) new ExpandoObjectConverter(), #endif -#if (!(PORTABLE40)) +#if !(PORTABLE40) new XmlNodeConverter(), #endif #if !(NETFX_CORE || PORTABLE40 || PORTABLE) @@ -112,11 +112,12 @@ namespace Newtonsoft.Json.Serialization new DataTableConverter(), #endif #if NETFX_CORE - new JsonValueConverter(), + new JsonValueConverter(), #endif new KeyValuePairConverter(), new BsonObjectIdConverter(), - new RegexConverter() + new RegexConverter(), + new DiscriminatedUnionConverter() }; private static Dictionary _sharedContractCache;