I am attempting to use Newtonsoft.json DefaultContractResolver to output json. When the ContractResolver finds a class of type IVariant, it sets the output based upon the IVariant's "TypeName"
Without my ContractResolver code the json would look something like this:
{
"Data" :
{
"Aim" :
{
"Joystick" :
{
"Test" : "Test"
},
"Button" :
{
"Test" : "Test2"
}
}
}
}
and with my resolver, It replaces "Aim" with either Joystick, or Button, and look like this:
{
"Data" :
{
"Button" :
{
"Test" : "Test2"
}
}
}
Below is the full code I used.
public class JsonTester
{
public void Test()
{
var d = new Outer();
var json = JsonConvert.SerializeObject(d, new JsonSerializerSettings()
{
ContractResolver = new JsonContractResolver()
});
}
}
public interface IVariant
{
object Data { get; }
string TypeName { get; set; }
}
public interface IVariantData
{
}
[DataContract]
public class Variant<TEnum, TData> : IVariant where TEnum : struct where TData : class, IVariantData, new()
{
[DataMember]
public TData Data { get; }
public string TypeName { get; set; }
object IVariant.Data => Data;
public Variant()
{
Data = new TData();
}
}
[DataContract]
public sealed class VariantHolder
{
[DataContract]
public class Joystick
{
[DataMember]
public string Test { get; set; } = "Test";
}
[DataContract]
public class Button
{
[DataMember]
public string Test2 { get; set; } = "Test2";
}
[DataContract]
public sealed class AimΞVariant : Variant<AimΞVariant.VariantEnum, AimΞVariant.VariantData>
{
public AimΞVariant()
{
TypeName = "Button";
}
public enum VariantEnum : byte
{
Joystick,
Button
}
[DataContract]
public sealed class VariantData : IVariantData
{
[DataMember]
Joystick _Joystick = new Joystick();
[DataMember]
Button _Button = new Button();
public Button Button => _Button;
public Joystick Joystick => _Joystick;
}
}
[DataMember]
AimΞVariant Aim = new AimΞVariant();
}
[DataContract]
public sealed class Outer
{
[DataMember]
VariantHolder Data = new VariantHolder();
}
public class JsonContractResolver : DefaultContractResolver
{
public JsonContractResolver()
{
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
return base.CreateProperty(member, memberSerialization);
}
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var list = type.GetProperties().Where(x => x.GetCustomAttributes().Any(a => a.GetType() == typeof(DataMemberAttribute))).Select(p =>
{
var member = p.GetCustomAttribute<DataMemberAttribute>();
return new JsonProperty()
{
PropertyName = p.Name,
PropertyType = p.PropertyType,
Readable = true,
Writable = true,
ValueProvider = CreateMemberValueProvider(p),
DefaultValueHandling = DefaultValueHandling.Include
};
}).Concat(
type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).Where(x => x.GetCustomAttributes().Any(a => a.GetType() == typeof(DataMemberAttribute))).Select(p =>
{
var member = p.GetCustomAttribute<DataMemberAttribute>();
return new JsonProperty()
{
PropertyName = p.Name,
PropertyType = p.FieldType,
Readable = true,
Writable = true,
ValueProvider = CreateMemberValueProvider(p),
DefaultValueHandling = DefaultValueHandling.Include
};
})).ToList();
return list;
}
protected override IValueProvider CreateMemberValueProvider(MemberInfo member)
{
Type type = member.MemberType == MemberTypes.Property ? ((PropertyInfo)member).PropertyType : ((FieldInfo)member).FieldType;
if (typeof(IVariant).IsAssignableFrom(type))
{
return new IVariantValueProvider(member, type);
}
return base.CreateMemberValueProvider(member);
}
}
internal class IVariantValueProvider : IValueProvider
{
private MemberInfo member;
private Type type;
public IVariantValueProvider(MemberInfo member, Type type)
{
this.member = member;
this.type = type;
}
public object GetValue(object target)
{
IVariant variant;
switch (member)
{
case PropertyInfo p:
variant = (IVariant)p.GetValue(target);
break;
case FieldInfo f:
variant = (IVariant)f.GetValue(target);
break;
default:
throw new InvalidCastException();
}
var dataType = variant.TypeName;
var property = variant.Data.GetType().GetProperty(dataType);
var value = property.GetValue(variant.Data);
return value;
}
public void SetValue(object target, object value)
{
throw new NotImplementedException();
}
}
The code crashes somewhere inside JsonConvert.SeralizeObject.
Newtonsoft.Json.JsonSerializationException: 'Error getting value from 'Data' on 'Iugo.Test.VariantHolder+Button'.' Inner Exception: InvalidCastException: Unable to cast object of type 'Button' to type 'Iugo.Test.Variant`2[Iugo.Test.VariantHolder+AimΞVariant+VariantEnum,Iugo.Test.VariantHolder+AimΞVariant+VariantData]'.