Skip to content

Commit

Permalink
Merge pull request #1608 from domaindrivendev/improve-support-for-dic…
Browse files Browse the repository at this point in the history
…tionaries-with-enum-keys

Honor serializer behavior when describing dictionaries with enum keys
  • Loading branch information
domaindrivendev committed Apr 2, 2020
2 parents eba592f + 8a397ee commit 316ddd0
Show file tree
Hide file tree
Showing 12 changed files with 359 additions and 330 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,57 +32,93 @@ public DataContract GetDataContractForType(Type type)
{
var primitiveTypeAndFormat = PrimitiveTypesAndFormats.ContainsKey(jsonContract.UnderlyingType)
? PrimitiveTypesAndFormats[jsonContract.UnderlyingType]
: Tuple.Create("string", (string)null);
: Tuple.Create(DataType.String, (string)null);

return DataContract.ForPrimitive(jsonContract.UnderlyingType, primitiveTypeAndFormat.Item1, primitiveTypeAndFormat.Item2);
return new DataContract(
dataType: primitiveTypeAndFormat.Item1,
format: primitiveTypeAndFormat.Item2,
underlyingType: jsonContract.UnderlyingType);
}

if (jsonContract is JsonPrimitiveContract && jsonContract.UnderlyingType.IsEnum)
{
var enumValues = GetSerializerEnumValuesFor(jsonContract);
var enumValues = GetSerializedEnumValuesFor(jsonContract);

var primitiveTypeAndFormat = (enumValues.Any(value => value.GetType() == typeof(string)))
? PrimitiveTypesAndFormats[typeof(string)]
: PrimitiveTypesAndFormats[jsonContract.UnderlyingType.GetEnumUnderlyingType()];

return DataContract.ForPrimitive(jsonContract.UnderlyingType, primitiveTypeAndFormat.Item1, primitiveTypeAndFormat.Item2, enumValues);
return new DataContract(
dataType: primitiveTypeAndFormat.Item1,
format: primitiveTypeAndFormat.Item2,
underlyingType: jsonContract.UnderlyingType,
enumValues: enumValues);
}

if (jsonContract is JsonDictionaryContract jsonDictionaryContract)
{
var keyType = jsonDictionaryContract.DictionaryKeyType ?? typeof(object);
var valueType = jsonDictionaryContract.DictionaryValueType ?? typeof(object);

return DataContract.ForDictionary(jsonDictionaryContract.UnderlyingType, keyType, valueType);
if (keyType.IsEnum)
{
// This is a special case where we can include named properties based on the enum values
var enumValues = GetDataContractForType(keyType).EnumValues;

var propertyNames = enumValues.Any(value => value is string)
? enumValues.Cast<string>()
: keyType.GetEnumNames();

return new DataContract(
dataType: DataType.Object,
underlyingType: jsonDictionaryContract.UnderlyingType,
properties: propertyNames.Select(name => new DataProperty(name, valueType)));
}

return new DataContract(
dataType: DataType.Object,
underlyingType: jsonDictionaryContract.UnderlyingType,
additionalPropertiesType: valueType);
}

if (jsonContract is JsonArrayContract jsonArrayContract)
{
var itemType = jsonArrayContract.CollectionItemType ?? typeof(object);
return DataContract.ForArray(jsonArrayContract.UnderlyingType, itemType);
return new DataContract(
dataType: DataType.Array,
underlyingType: jsonArrayContract.UnderlyingType,
arrayItemType: jsonArrayContract.CollectionItemType ?? typeof(object));
}

if (jsonContract is JsonObjectContract jsonObjectContract)
{
return DataContract.ForObject(
jsonContract.UnderlyingType,
GetSerializerMembersFor(jsonObjectContract),
jsonObjectContract.ExtensionDataValueType);
return new DataContract(
dataType: DataType.Object,
underlyingType: jsonObjectContract.UnderlyingType,
properties: GetDataPropertiesFor(jsonObjectContract),
additionalPropertiesType: jsonObjectContract.ExtensionDataValueType);
}

if (jsonContract is JsonLinqContract jsonLinqContract)
if (jsonContract.UnderlyingType == typeof(JArray))
{
if (jsonLinqContract.UnderlyingType == typeof(JArray))
return DataContract.ForArray(typeof(JArray), typeof(JToken));
return new DataContract(
dataType: DataType.Array,
underlyingType: jsonContract.UnderlyingType,
arrayItemType: typeof(JToken));
}

if (jsonLinqContract.UnderlyingType == typeof(JObject))
return DataContract.ForObject(typeof(JObject));
if (jsonContract.UnderlyingType == typeof(JObject))
{
return new DataContract(
dataType: DataType.Object,
underlyingType: jsonContract.UnderlyingType);
}

return DataContract.ForDynamic(jsonContract.UnderlyingType);
return new DataContract(
dataType: DataType.Unknown,
underlyingType: jsonContract.UnderlyingType);
}

private IEnumerable<object> GetSerializerEnumValuesFor(JsonContract jsonContract)
private IEnumerable<object> GetSerializedEnumValuesFor(JsonContract jsonContract)
{
var stringEnumConverter = (jsonContract.Converter as StringEnumConverter)
?? _serializerSettings.Converters.OfType<StringEnumConverter>().FirstOrDefault();
Expand Down Expand Up @@ -127,14 +163,14 @@ private string GetConvertedEnumName(string enumName, bool hasSpecifiedName, Stri
}
#endif

private IEnumerable<DataMember> GetSerializerMembersFor(JsonObjectContract jsonObjectContract)
private IEnumerable<DataProperty> GetDataPropertiesFor(JsonObjectContract jsonObjectContract)
{
if (jsonObjectContract.UnderlyingType == typeof(object))
{
return null;
}

var serializerMembers = new List<DataMember>();
var dataProperties = new List<DataProperty>();

foreach (var jsonProperty in jsonObjectContract.Properties)
{
Expand All @@ -144,48 +180,48 @@ private IEnumerable<DataMember> GetSerializerMembersFor(JsonObjectContract jsonO
? jsonProperty.Required
: jsonObjectContract.ItemRequired ?? Required.Default;

jsonProperty.TryGetMemberInfo(out MemberInfo memberInfo);

var isSetViaConstructor = jsonProperty.DeclaringType.GetConstructors()
.SelectMany(c => c.GetParameters())
.Any(p => string.Equals(p.Name, jsonProperty.PropertyName, StringComparison.OrdinalIgnoreCase));

serializerMembers.Add(
new DataMember(
jsonProperty.TryGetMemberInfo(out MemberInfo memberInfo);

dataProperties.Add(
new DataProperty(
name: jsonProperty.PropertyName,
memberType: jsonProperty.PropertyType,
memberInfo: memberInfo,
isRequired: (required == Required.Always || required == Required.AllowNull),
isNullable: (required == Required.AllowNull || required == Required.Default) && jsonProperty.PropertyType.IsReferenceOrNullableType(),
isReadOnly: jsonProperty.Readable && !jsonProperty.Writable && !isSetViaConstructor,
isWriteOnly: jsonProperty.Writable && !jsonProperty.Readable));
isWriteOnly: jsonProperty.Writable && !jsonProperty.Readable,
memberType: jsonProperty.PropertyType,
memberInfo: memberInfo));
}

return serializerMembers;
return dataProperties;
}

private static readonly Dictionary<Type, Tuple<string, string>> PrimitiveTypesAndFormats = new Dictionary<Type, Tuple<string, string>>
private static readonly Dictionary<Type, Tuple<DataType, string>> PrimitiveTypesAndFormats = new Dictionary<Type, Tuple<DataType, string>>
{
[ typeof(bool) ] = Tuple.Create("boolean", (string)null),
[ typeof(byte) ] = Tuple.Create("integer", "int32"),
[ typeof(sbyte) ] = Tuple.Create("integer", "int32"),
[ typeof(short) ] = Tuple.Create("integer", "int32"),
[ typeof(ushort) ] = Tuple.Create("integer", "int32"),
[ typeof(int) ] = Tuple.Create("integer", "int32"),
[ typeof(uint) ] = Tuple.Create("integer", "int32"),
[ typeof(long) ] = Tuple.Create("integer", "int64"),
[ typeof(ulong) ] = Tuple.Create("integer", "int64"),
[ typeof(float) ] = Tuple.Create("number", "float"),
[ typeof(double) ] = Tuple.Create("number", "double"),
[ typeof(decimal) ] = Tuple.Create("number", "double"),
[ typeof(byte[]) ] = Tuple.Create("string", "byte"),
[ typeof(string) ] = Tuple.Create("string", (string)null),
[ typeof(char) ] = Tuple.Create("string", (string)null),
[ typeof(DateTime) ] = Tuple.Create("string", "date-time"),
[ typeof(DateTimeOffset) ] = Tuple.Create("string", "date-time"),
[ typeof(Guid) ] = Tuple.Create("string", "uuid"),
[ typeof(Uri) ] = Tuple.Create("string", "uri"),
[ typeof(TimeSpan) ] = Tuple.Create("string", "date-span")
[ typeof(bool) ] = Tuple.Create(DataType.Boolean, (string)null),
[ typeof(byte) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(sbyte) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(short) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(ushort) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(int) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(uint) ] = Tuple.Create(DataType.Integer, "int32"),
[ typeof(long) ] = Tuple.Create(DataType.Integer, "int64"),
[ typeof(ulong) ] = Tuple.Create(DataType.Integer, "int64"),
[ typeof(float) ] = Tuple.Create(DataType.Number, "float"),
[ typeof(double) ] = Tuple.Create(DataType.Number, "double"),
[ typeof(decimal) ] = Tuple.Create(DataType.Number, "double"),
[ typeof(byte[]) ] = Tuple.Create(DataType.String, "byte"),
[ typeof(string) ] = Tuple.Create(DataType.String, (string)null),
[ typeof(char) ] = Tuple.Create(DataType.String, (string)null),
[ typeof(DateTime) ] = Tuple.Create(DataType.String, "date-time"),
[ typeof(DateTimeOffset) ] = Tuple.Create(DataType.String, "date-time"),
[ typeof(Guid) ] = Tuple.Create(DataType.String, "uuid"),
[ typeof(Uri) ] = Tuple.Create(DataType.String, "uri"),
[ typeof(TimeSpan) ] = Tuple.Create(DataType.String, "date-span")
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,130 +11,76 @@ public interface IDataContractResolver

public class DataContract
{
public static DataContract ForPrimitive(
Type type,
string dataType,
string dataFormat = null,
IEnumerable<object> enumValues = null)
public DataContract(
DataType dataType,
Type underlyingType,
string format = null,
IEnumerable<object> enumValues = null,
IEnumerable<DataProperty> properties = null,
Type additionalPropertiesType = null,
Type arrayItemType = null)
{
return new DataContract
{
IsPrimitive = true,
Type = type,
DataType = dataType,
DataFormat = dataFormat,
EnumValues = enumValues,
};
DataType = dataType;
Format = format;
EnumValues = enumValues;
Properties = properties;
UnderlyingType = underlyingType;
AdditionalPropertiesType = additionalPropertiesType;
ArrayItemType = arrayItemType;
}

public static DataContract ForDictionary(Type type, Type keyType, Type valueType)
{
return new DataContract
{
IsDictionary = true,
Type = type,
DictionaryKeyType = keyType,
DictionaryValueType = valueType,
DataType = "object",
};
}

public static DataContract ForArray(Type type, Type itemType)
{
return new DataContract
{
IsArray = true,
Type = type,
ArrayItemType = itemType,
DataType = "array",
};
}

public static DataContract ForObject(
Type type,
IEnumerable<DataMember> members = null,
Type extensionDataValueType = null)
{
return new DataContract
{
IsObject = true,
Type = type,
Members = members,
ExtensionDataValueType = extensionDataValueType,
DataType = "object",
};
}

public static DataContract ForDynamic(Type type)
{
return new DataContract
{
IsDynamic = true,
Type = type
};
}

public bool IsPrimitive { get; private set; }

public bool IsDictionary { get; private set; }

public bool IsArray { get; private set; }

public bool IsObject { get; private set; }

public bool IsDynamic { get; private set; }

public Type Type { get; private set; }

public Type DictionaryKeyType { get; private set; }

public Type DictionaryValueType { get; private set; }

public Type ArrayItemType { get; private set; }

public IEnumerable<DataMember> Members { get; private set; }

public Type ExtensionDataValueType { get; private set; }

public string DataType { get; private set; }

public string DataFormat { get; private set; }
public DataType DataType { get; }
public string Format { get; }
public IEnumerable<object> EnumValues { get; }
public IEnumerable<DataProperty> Properties { get; }
public Type UnderlyingType { get; }
public Type AdditionalPropertiesType { get; }
public Type ArrayItemType { get; }
}

public IEnumerable<object> EnumValues { get; set; }
public enum DataType
{
Unknown,
Boolean,
Integer,
Number,
String,
Object,
Array
}

public class DataMember
public class DataProperty
{
public DataMember(
public DataProperty(
string name,
Type memberType,
MemberInfo memberInfo,
bool isRequired = false,
bool isNullable = false,
bool isReadOnly = false,
bool isWriteOnly = false)
bool isWriteOnly = false,
MemberInfo memberInfo = null)
{
Name = name;
MemberType = memberType;
MemberInfo = memberInfo;
IsRequired = isRequired;
IsNullable = isNullable;
IsReadOnly = isReadOnly;
IsWriteOnly = isWriteOnly;
MemberType = memberType;
MemberInfo = memberInfo;
}

public string Name { get; }

public Type MemberType { get; }

public MemberInfo MemberInfo { get; }

public bool IsRequired { get; }

public bool IsNullable { get; }

public bool IsReadOnly { get; }

public bool IsWriteOnly { get; }

public Type MemberType { get; }

public MemberInfo MemberInfo { get; }
}
}

0 comments on commit 316ddd0

Please sign in to comment.