mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-17 02:51:45 -05:00
add flag converters nullable serialization (#857)
This commit is contained in:
committed by
Kevin Cunnane
parent
55c82fbcc4
commit
c6e3b33c35
@@ -0,0 +1,3 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("Microsoft.SqlTools.Hosting.UnitTests")]
|
||||||
@@ -18,7 +18,7 @@ namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
|
|||||||
public override bool CanRead => true;
|
public override bool CanRead => true;
|
||||||
|
|
||||||
#region Public Methods
|
#region Public Methods
|
||||||
|
|
||||||
public override bool CanConvert(Type objectType)
|
public override bool CanConvert(Type objectType)
|
||||||
{
|
{
|
||||||
return objectType.IsEnum && objectType.GetCustomAttribute(typeof(FlagsAttribute)) != null;
|
return objectType.IsEnum && objectType.GetCustomAttribute(typeof(FlagsAttribute)) != null;
|
||||||
@@ -26,16 +26,21 @@ 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)
|
||||||
int[] values = JArray.Load(reader).Values<int>().ToArray();
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
FieldInfo[] enumFields = objectType.GetFields(BindingFlags.Public | BindingFlags.Static);
|
int[] values = ((JArray)jToken).Values<int>().ToArray();
|
||||||
|
var pureType = NullableUtils.GetUnderlyingTypeIfNullable(objectType);
|
||||||
|
|
||||||
|
FieldInfo[] enumFields = pureType.GetFields(BindingFlags.Public | BindingFlags.Static);
|
||||||
int setFlags = 0;
|
int setFlags = 0;
|
||||||
foreach (FieldInfo enumField in enumFields)
|
foreach (FieldInfo enumField in enumFields)
|
||||||
{
|
{
|
||||||
int enumValue = (int) enumField.GetValue(null);
|
int enumValue = (int)enumField.GetValue(null);
|
||||||
|
|
||||||
// If there is a serialize value set for the enum value, look for that instead of the int value
|
// If there is a serialize value set for the enum value, look for that instead of the int value
|
||||||
SerializeValueAttribute serializeValue = enumField.GetCustomAttribute<SerializeValueAttribute>();
|
SerializeValueAttribute serializeValue = enumField.GetCustomAttribute<SerializeValueAttribute>();
|
||||||
int searchValue = serializeValue?.Value ?? enumValue;
|
int searchValue = serializeValue?.Value ?? enumValue;
|
||||||
@@ -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)
|
||||||
@@ -56,22 +61,22 @@ namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
|
|||||||
{
|
{
|
||||||
// Make sure the flag is set before doing expensive reflection
|
// Make sure the flag is set before doing expensive reflection
|
||||||
int enumValue = (int)enumField.GetValue(null);
|
int enumValue = (int)enumField.GetValue(null);
|
||||||
if (((int) value & enumValue) == 0)
|
if (((int)value & enumValue) == 0)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is a serialize value set for the member, use that instead of the int value
|
// If there is a serialize value set for the member, use that instead of the int value
|
||||||
SerializeValueAttribute serializeValue = enumField.GetCustomAttribute<SerializeValueAttribute>();
|
SerializeValueAttribute serializeValue = enumField.GetCustomAttribute<SerializeValueAttribute>();
|
||||||
int flagValue = serializeValue?.Value ?? enumValue;
|
int flagValue = serializeValue?.Value ?? enumValue;
|
||||||
setFlags.Add(flagValue);
|
setFlags.Add(flagValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
string joinedFlags = string.Join(", ", setFlags);
|
string joinedFlags = string.Join(", ", setFlags);
|
||||||
writer.WriteRawValue($"[{joinedFlags}]");
|
writer.WriteRawValue($"[{joinedFlags}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion Public Methods
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
internal class SerializeValueAttribute : Attribute
|
internal class SerializeValueAttribute : Attribute
|
||||||
@@ -82,6 +87,6 @@ namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
|
|||||||
{
|
{
|
||||||
Value = value;
|
Value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,33 +14,32 @@ using Newtonsoft.Json.Serialization;
|
|||||||
namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
|
namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
|
||||||
{
|
{
|
||||||
internal class FlagsStringConverter : JsonConverter
|
internal class FlagsStringConverter : JsonConverter
|
||||||
{
|
{
|
||||||
public override bool CanWrite => true;
|
public override bool CanWrite => true;
|
||||||
public override bool CanRead => true;
|
public override bool CanRead => true;
|
||||||
|
|
||||||
#region Public Methods
|
#region Public Methods
|
||||||
|
|
||||||
public override bool CanConvert(Type objectType)
|
public override bool CanConvert(Type objectType)
|
||||||
{
|
{
|
||||||
return objectType.IsEnum && objectType.GetCustomAttribute(typeof(FlagsAttribute)) != null;
|
return objectType.IsEnum && objectType.GetCustomAttribute(typeof(FlagsAttribute)) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
// If there is a serialize value set for the enum value, look for the instead of the int value
|
// If there is a serialize value set for the enum value, look for the instead of the int value
|
||||||
SerializeValueAttribute serializeValue = enumField.GetCustomAttribute<SerializeValueAttribute>();
|
SerializeValueAttribute serializeValue = enumField.GetCustomAttribute<SerializeValueAttribute>();
|
||||||
string searchValue = serializeValue?.Value ?? enumField.Name;
|
string searchValue = serializeValue?.Value ?? enumField.Name;
|
||||||
@@ -48,15 +47,15 @@ namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
|
|||||||
{
|
{
|
||||||
searchValue = char.ToLowerInvariant(searchValue[0]) + searchValue.Substring(1);
|
searchValue = char.ToLowerInvariant(searchValue[0]) + searchValue.Substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the value is in the json array, or the int value into the flags
|
// If the value is in the json array, or the int value into the flags
|
||||||
if (Array.IndexOf(values, searchValue) >= 0)
|
if (Array.IndexOf(values, searchValue) >= 0)
|
||||||
{
|
{
|
||||||
setFlags |= (int) enumField.GetValue(null);
|
setFlags |= (int)enumField.GetValue(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@@ -66,12 +65,12 @@ namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
|
|||||||
foreach (FieldInfo enumField in enumFields)
|
foreach (FieldInfo enumField in enumFields)
|
||||||
{
|
{
|
||||||
// Make sure the flag is set before doing any other work
|
// Make sure the flag is set before doing any other work
|
||||||
int enumValue = (int) enumField.GetValue(null);
|
int enumValue = (int)enumField.GetValue(null);
|
||||||
if (((int) value & enumValue) == 0)
|
if (((int)value & enumValue) == 0)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is a serialize value set for the member, use that instead of the int value
|
// If there is a serialize value set for the member, use that instead of the int value
|
||||||
SerializeValueAttribute serializeValue = enumField.GetCustomAttribute<SerializeValueAttribute>();
|
SerializeValueAttribute serializeValue = enumField.GetCustomAttribute<SerializeValueAttribute>();
|
||||||
string flagValue = serializeValue?.Value ?? enumField.Name;
|
string flagValue = serializeValue?.Value ?? enumField.Name;
|
||||||
@@ -85,9 +84,9 @@ namespace Microsoft.SqlTools.DataProtocol.Contracts.Utilities
|
|||||||
string joinedFlags = string.Join(", ", setFlags);
|
string joinedFlags = string.Join(", ", setFlags);
|
||||||
writer.WriteRawValue($"[{joinedFlags}]");
|
writer.WriteRawValue($"[{joinedFlags}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion Public Methods
|
||||||
|
|
||||||
[AttributeUsage(AttributeTargets.Field)]
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
internal class SerializeValueAttribute : Attribute
|
internal class SerializeValueAttribute : Attribute
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user