Last active
October 26, 2018 14:35
-
-
Save yringler/b6f32302e1acdc63af894281ef39f25f to your computer and use it in GitHub Desktop.
A contract resolver for JsonApiSerializer which treats integer ids as string ids
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace Chabad.Data.Entities | |
{ | |
public interface IDatabaseRecord | |
{ | |
int Id { get; set; } | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Web; | |
namespace Api.Utils.Json | |
{ | |
/// <summary> | |
/// Specify what field provides the resource id for JSON:API | |
/// </summary | |
[AttributeUsage(AttributeTargets.Property)] | |
public sealed class JsonApiIdAttribute : Attribute | |
{ | |
/// <summary> | |
/// The name of the property which contains the resource id. | |
/// </summary> | |
public string Name { get; } | |
public JsonApiIdAttribute(string name) | |
{ | |
Name = name; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Chabad.Data.Entities; | |
using JsonApiSerializer.JsonConverters; | |
using Newtonsoft.Json; | |
using Newtonsoft.Json.Serialization; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using System.Web; | |
namespace Api.Utils.Json | |
{ | |
/// <summary> | |
/// A contract resolver for JSON:API which treats integer ids as strings. | |
/// </summary> | |
public class JsonApiIdContractResolver : JsonApiSerializer.ContractResolvers.JsonApiContractResolver | |
{ | |
public JsonApiIdContractResolver() | |
{ | |
} | |
public JsonApiIdContractResolver(JsonConverter converter) : base(converter) | |
{ | |
} | |
/// <summary> | |
/// Calls base method, unless the property name is id. | |
/// Then it returns the appropriate custom value provider which is able to get the id value. | |
/// </summary> | |
protected override IValueProvider CreateMemberValueProvider(MemberInfo member) | |
{ | |
if (member.Name.ToLower() == "id" && typeof(IDatabaseRecord).IsAssignableFrom(member.DeclaringType)) | |
{ | |
var idSourceAttribute = member.GetCustomAttribute<JsonApiIdAttribute>(); | |
if (idSourceAttribute == null) | |
return RecordIdAsStringValueProvider.Instance; | |
else | |
return new RecordIdFromOtherValueProvider(member.DeclaringType, idSourceAttribute.Name); | |
} | |
return base.CreateMemberValueProvider(member); | |
} | |
/// <summary> | |
/// Calls base method, except that if the property is the id, always specify the type as string. | |
/// </summary> | |
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) | |
{ | |
var property = base.CreateProperty(member, memberSerialization); | |
if (member.Name.ToLower() == "id") | |
property.PropertyType = typeof(string); | |
return property; | |
} | |
/// <summary> | |
/// Value provider which attempts to get/set an the id of a <see cref="IDatabaseRecord"/> from a string. | |
/// </summary> | |
private class RecordIdAsStringValueProvider : IValueProvider | |
{ | |
public static RecordIdAsStringValueProvider Instance { get; } = new RecordIdAsStringValueProvider(); | |
private RecordIdAsStringValueProvider() { } | |
public object GetValue(object target) | |
{ | |
// TODO: Use attribute to link id to backing field if desired, move logic to CreateMemberValueProvider this(constructor). | |
if (target is IDatabaseRecord record) | |
return record.Id.ToString(); | |
string errorMessage = $@" | |
Error: must be called with {nameof(IDatabaseRecord)}. | |
Was called with type: {target.GetType().FullName}. | |
"; | |
throw new ArgumentException(errorMessage); | |
} | |
public void SetValue(object target, object value) | |
{ | |
if (target is IDatabaseRecord record) | |
{ | |
record.Id = Convert.ToInt32(value); | |
} | |
else | |
{ | |
string errorMessage = $@" | |
Error: must be called with {nameof(IDatabaseRecord)}. | |
Was called with type: {target.GetType().FullName}. | |
"; | |
throw new ArgumentException(errorMessage); | |
} | |
} | |
} | |
/// <summary> | |
/// Value provider which attempts to get/set the id of an <see cref="IDatabaseRecord"/> from another property. | |
/// </summary> | |
private class RecordIdFromOtherValueProvider : IValueProvider | |
{ | |
/// <summary> | |
/// The type of the class which this instance is providing values for. | |
/// </summary> | |
private readonly Type declaringType; | |
/// <summary> | |
/// The name of the property which holds the ID data. | |
/// </summary> | |
private readonly string name; | |
public RecordIdFromOtherValueProvider(Type declaringType, string name) | |
{ | |
this.declaringType = declaringType; | |
this.name = name; | |
} | |
public object GetValue(object target) | |
{ | |
return declaringType.GetProperty(name).GetValue(target).ToString(); | |
} | |
public void SetValue(object target, object value) | |
{ | |
int intValue = Convert.ToInt32(value); | |
declaringType.GetProperty(name).SetValue(target, intValue); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment