add flag converters nullable serialization (#857)

This commit is contained in:
sergei-boguslavski
2019-08-29 10:35:45 -07:00
committed by Kevin Cunnane
parent 55c82fbcc4
commit c6e3b33c35
6 changed files with 205 additions and 32 deletions

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.SqlTools.Hosting.UnitTests")]

View File

@@ -26,11 +26,16 @@ namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
// TODO: Fix to handle nullables properly var jToken = JToken.Load(reader);
if (jToken.Type == JTokenType.Null)
{
return null;
}
int[] values = JArray.Load(reader).Values<int>().ToArray(); int[] values = ((JArray)jToken).Values<int>().ToArray();
var pureType = NullableUtils.GetUnderlyingTypeIfNullable(objectType);
FieldInfo[] enumFields = objectType.GetFields(BindingFlags.Public | BindingFlags.Static); FieldInfo[] enumFields = pureType.GetFields(BindingFlags.Public | BindingFlags.Static);
int setFlags = 0; int setFlags = 0;
foreach (FieldInfo enumField in enumFields) foreach (FieldInfo enumField in enumFields)
{ {
@@ -45,7 +50,7 @@ namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
} }
} }
return Enum.ToObject(objectType, setFlags); return Enum.ToObject(pureType, setFlags);
} }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
@@ -71,7 +76,7 @@ namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
writer.WriteRawValue($"[{joinedFlags}]"); writer.WriteRawValue($"[{joinedFlags}]");
} }
#endregion #endregion Public Methods
[AttributeUsage(AttributeTargets.Field)] [AttributeUsage(AttributeTargets.Field)]
internal class SerializeValueAttribute : Attribute internal class SerializeValueAttribute : Attribute

View File

@@ -27,8 +27,6 @@ namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{ {
// TODO: Fix to handle nullables properly
JToken jToken = JToken.Load(reader); JToken jToken = JToken.Load(reader);
if (jToken.Type == JTokenType.Null) if (jToken.Type == JTokenType.Null)
{ {
@@ -36,8 +34,9 @@ namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
} }
string[] values = ((JArray)jToken).Values<string>().ToArray(); string[] values = ((JArray)jToken).Values<string>().ToArray();
var pureType = NullableUtils.GetUnderlyingTypeIfNullable(objectType);
FieldInfo[] enumFields = objectType.GetFields(BindingFlags.Public | BindingFlags.Static); FieldInfo[] enumFields = pureType.GetFields(BindingFlags.Public | BindingFlags.Static);
int setFlags = 0; int setFlags = 0;
foreach (FieldInfo enumField in enumFields) foreach (FieldInfo enumField in enumFields)
{ {
@@ -56,7 +55,7 @@ namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
} }
} }
return Enum.ToObject(objectType, setFlags); return Enum.ToObject(pureType, setFlags);
} }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
@@ -86,7 +85,7 @@ namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
writer.WriteRawValue($"[{joinedFlags}]"); writer.WriteRawValue($"[{joinedFlags}]");
} }
#endregion #endregion Public Methods
[AttributeUsage(AttributeTargets.Field)] [AttributeUsage(AttributeTargets.Field)]
internal class SerializeValueAttribute : Attribute internal class SerializeValueAttribute : Attribute

View File

@@ -0,0 +1,36 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
{
internal static class NullableUtils
{
/// <summary>
/// Determines whether the type is <see cref="Nullable{T}"/>.
/// </summary>
/// <param name="t">The type.</param>
/// <returns>
/// <c>true</c> if <paramref name="t"/> is <see cref="Nullable{T}"/>; otherwise, <c>false</c>.
/// </returns>
public static bool IsNullable(Type t)
{
return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>);
}
/// <summary>
/// Unwraps the <see cref="Nullable{T}"/> if necessary and returns the underlying value type.
/// </summary>
/// <param name="t">The type.</param>
/// <returns>The underlying value type the <see cref="Nullable{T}"/> type was produced from,
/// or the <paramref name="t"/> type if the type is not <see cref="Nullable{T}"/>.
/// </returns>
public static Type GetUnderlyingTypeIfNullable(Type t)
{
return IsNullable(t) ? Nullable.GetUnderlyingType(t) : t;
}
}
}

View File

@@ -0,0 +1,65 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.DataProtocol.Contracts.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using Xunit;
namespace Microsoft.SqlTools.Hosting.UnitTests.Contracts.Utilities
{
public class FlagsIntConverterTests
{
[Fact]
public void NullableValueCanBeDeserialized()
{
var jsonObject = JObject.Parse("{\"optionalValue\": [1, 2]}");
var contract = jsonObject.ToObject<DataContract>();
Assert.NotNull(contract);
Assert.NotNull(contract.OptionalValue);
Assert.Equal(TestFlags.FirstItem | TestFlags.SecondItem, contract.OptionalValue);
}
[Fact]
public void RegularValueCanBeDeserialized()
{
var jsonObject = JObject.Parse("{\"Value\": [1, 3]}");
var contract = jsonObject.ToObject<DataContract>();
Assert.NotNull(contract);
Assert.Equal(TestFlags.FirstItem | TestFlags.ThirdItem, contract.Value);
}
[Fact]
public void ExplicitNullCanBeDeserialized()
{
var jsonObject = JObject.Parse("{\"optionalValue\": null}");
var contract = jsonObject.ToObject<DataContract>();
Assert.NotNull(contract);
Assert.Null(contract.OptionalValue);
}
[Flags]
[JsonConverter(typeof(FlagsIntConverter))]
private enum TestFlags
{
[FlagsIntConverter.SerializeValue(1)]
FirstItem = 1 << 0,
[FlagsIntConverter.SerializeValue(2)]
SecondItem = 1 << 1,
[FlagsIntConverter.SerializeValue(3)]
ThirdItem = 1 << 2,
}
private class DataContract
{
public TestFlags? OptionalValue { get; set; }
public TestFlags Value { get; set; }
}
}
}

View File

@@ -0,0 +1,65 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System;
using Microsoft.SqlTools.DataProtocol.Contracts.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
namespace Microsoft.SqlTools.Hosting.UnitTests.Contracts.Utilities
{
public class FlagsStringConverterTests
{
[Fact]
public void NullableValueCanBeDeserialized()
{
var jsonObject = JObject.Parse("{\"optionalValue\": [\"First\", \"Second\"]}");
var contract = jsonObject.ToObject<DataContract>();
Assert.NotNull(contract);
Assert.NotNull(contract.OptionalValue);
Assert.Equal(TestFlags.FirstItem | TestFlags.SecondItem, contract.OptionalValue);
}
[Fact]
public void RegularValueCanBeDeserialized()
{
var jsonObject = JObject.Parse("{\"Value\": [\"First\", \"Third\"]}");
var contract = jsonObject.ToObject<DataContract>();
Assert.NotNull(contract);
Assert.Equal(TestFlags.FirstItem | TestFlags.ThirdItem, contract.Value);
}
[Fact]
public void ExplicitNullCanBeDeserialized()
{
var jsonObject = JObject.Parse("{\"optionalValue\": null}");
var contract = jsonObject.ToObject<DataContract>();
Assert.NotNull(contract);
Assert.Null(contract.OptionalValue);
}
[Flags]
[JsonConverter(typeof(FlagsStringConverter))]
private enum TestFlags
{
[FlagsStringConverter.SerializeValue("First")]
FirstItem = 1 << 0,
[FlagsStringConverter.SerializeValue("Second")]
SecondItem = 1 << 1,
[FlagsStringConverter.SerializeValue("Third")]
ThirdItem = 1 << 2,
}
private class DataContract
{
public TestFlags? OptionalValue { get; set; }
public TestFlags Value { get; set; }
}
}
}