Как получить имя <T> из родового типа и передать его в JsonProperty()?
Я получаю следующую ошибку с кодом ниже:
"Ссылка на объект требуется для нестатического поля, метода или свойство 'Response.PropName'"
Код:
public class Response<T> : Response
{
private string PropName
{
get
{
return typeof(T).Name;
}
}
[JsonProperty(PropName)]
public T Data { get; set; }
}
Ответы
Ответ 1
То, что вы пытаетесь сделать, возможно, но не тривиально и не может быть выполнено только с помощью встроенных атрибутов от JSON.NET. Вам понадобится настраиваемый атрибут и пользовательский разрешитель.
В этом решении я придумал:
Объявите этот настраиваемый атрибут:
[AttributeUsage(AttributeTargets.Property)]
class JsonPropertyGenericTypeNameAttribute : Attribute
{
public int TypeParameterPosition { get; }
public JsonPropertyGenericTypeNameAttribute(int position)
{
TypeParameterPosition = position;
}
}
Примените его к свойству Data
public class Response<T> : Response
{
[JsonPropertyGenericTypeName(0)]
public T Data { get; set; }
}
(0 - позиция T
в Response<T>
параметрах типового типа)
Объявите следующий разрешитель контракта, который будет искать атрибут JsonPropertyGenericTypeName
и получить фактическое имя аргумента типа:
class GenericTypeNameContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
var attr = member.GetCustomAttribute<JsonPropertyGenericTypeNameAttribute>();
if (attr != null)
{
var type = member.DeclaringType;
if (!type.IsGenericType)
throw new InvalidOperationException($"{type} is not a generic type");
if (type.IsGenericTypeDefinition)
throw new InvalidOperationException($"{type} is a generic type definition, it must be a constructed generic type");
var typeArgs = type.GetGenericArguments();
if (attr.TypeParameterPosition >= typeArgs.Length)
throw new ArgumentException($"Can't get type argument at position {attr.TypeParameterPosition}; {type} has only {typeArgs.Length} type arguments");
prop.PropertyName = typeArgs[attr.TypeParameterPosition].Name;
}
return prop;
}
}
Сериализовать этот резольвер в настройках сериализации:
var settings = new JsonSerializerSettings { ContractResolver = new GenericTypeNameContractResolver() };
string json = JsonConvert.SerializeObject(response, settings);
Это даст следующий результат для Response<Foo>
{
"Foo": {
"Id": 0,
"Name": null
}
}
Ответ 2
Здесь возможно более простой способ его достижения. Все, что вам нужно сделать, - это расширить JObject Response, например:
public class Response<T>: Newtonsoft.Json.Linq.JObject
{
private static string TypeName = (typeof(T)).Name;
private T _data;
public T Data {
get { return _data; }
set {
_data = value;
this[TypeName] = Newtonsoft.Json.Linq.JToken.FromObject(_data);
}
}
}
Если вы это сделаете, следующее будет работать так, как вы ожидаете:
static void Main(string[] args)
{
var p1 = new Response<Int32>();
p1.Data = 5;
var p2 = new Response<string>();
p2.Data = "Message";
Console.Out.WriteLine("First: " + JsonConvert.SerializeObject(p1));
Console.Out.WriteLine("Second: " + JsonConvert.SerializeObject(p2));
}
Вывод:
First: {"Int32":5}
Second: {"String":"Message"}
Если вы не можете Response<T>
расширять JObject, потому что вам действительно нужно расширить Response, вы могли бы сам Response расширять JObject, а затем Response<T>
расширять Response, как раньше. Он должен работать одинаково.
Ответ 3
@Томас Левеске: Хорошо. Поэтому скажем, что вы не можете расширять JObject в Response<T>
, потому что вам нужно расширить существующий класс Response. Здесь вы можете реализовать одно и то же решение:
public class Payload<T> : Newtonsoft.Json.Linq.JObject {
private static string TypeName = (typeof(T)).Name;
private T _data;
public T Data {
get { return _data; }
set {
_data = value;
this[TypeName] = Newtonsoft.Json.Linq.JToken.FromObject(_data);
}
}
}
//Response is a pre-existing class...
public class Response<T>: Response {
private Payload<T> Value;
public Response(T arg) {
Value = new Payload<T>() { Data = arg };
}
public static implicit operator JObject(Response<T> arg) {
return arg.Value;
}
public string Serialize() {
return Value.ToString();
}
}
Итак, теперь для Сериализации класса существуют следующие параметры:
static void Main(string[] args) {
var p1 = new Response<Int32>(5);
var p2 = new Response<string>("Message");
JObject p3 = new Response<double>(0.0);
var p4 = (JObject) new Response<DateTime>(DateTime.Now);
Console.Out.WriteLine(p1.Serialize());
Console.Out.WriteLine(p2.Serialize());
Console.Out.WriteLine(JsonConvert.SerializeObject(p3));
Console.Out.WriteLine(JsonConvert.SerializeObject(p4));
}
Результат будет выглядеть примерно так:
{"Int32":5}
{"String":"Message"}
{"Double":0.0}
{"DateTime":"2016-08-25T00:18:31.4882199-04:00"}