Skip to content

Instantly share code, notes, and snippets.

@yringler
Last active October 26, 2018 14:35
Show Gist options
  • Save yringler/b6f32302e1acdc63af894281ef39f25f to your computer and use it in GitHub Desktop.
Save yringler/b6f32302e1acdc63af894281ef39f25f to your computer and use it in GitHub Desktop.
A contract resolver for JsonApiSerializer which treats integer ids as string ids
using System;
namespace Chabad.Data.Entities
{
public interface IDatabaseRecord
{
int Id { get; set; }
}
}
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;
}
}
}
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