How to get the name of <T> from generic type and pass it into JsonProperty()?

喜你入骨 提交于 2019-12-17 16:34:36

问题


I get the following error with the code below:

"An object reference is required for the non-static field, method, or property 'Response.PropName'"

Code:

public class Response<T> : Response
{
    private string PropName
    {
        get
        {
            return typeof(T).Name;
        }
    }            
    [JsonProperty(PropName)]
    public T Data { get; set; }
}

回答1:


What you're trying to do is possible, but not trivial, and can't be done with only the built-in attributes from JSON.NET. You'll need a custom attribute, and a custom contract resolver.

Here's the solution I came up with:

Declare this custom attribute:

[AttributeUsage(AttributeTargets.Property)]
class JsonPropertyGenericTypeNameAttribute : Attribute
{
    public int TypeParameterPosition { get; }

    public JsonPropertyGenericTypeNameAttribute(int position)
    {
        TypeParameterPosition = position;
    }
}

Apply it to your Data property

public class Response<T> : Response
{
    [JsonPropertyGenericTypeName(0)]
    public T Data { get; set; }
}

(0 is the position of T in Response<T>'s generic type parameters)

Declare the following contract resolver, which will look for the JsonPropertyGenericTypeName attribute and get the actual name of the type argument:

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;
    }
}

Serialize with this resolver in your serialization settings:

var settings = new JsonSerializerSettings { ContractResolver = new GenericTypeNameContractResolver() };
string json = JsonConvert.SerializeObject(response, settings);

This will give the following output for Response<Foo>

{
  "Foo": {
    "Id": 0,
    "Name": null
  }
}



回答2:


Here's a potentially easier way to achieve it. All you need to do is to have Response extend JObject, like this:

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);   
        }
    }
}

If you do that, the following would work as you expect:

   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));
    }

Output:

First: {"Int32":5}
Second: {"String":"Message"}

In case you can't have Response<T> extend JObject, because you really need it to extend Response, you could have Response itself extend JObject, and then have Response<T> extend Response as before. It should work just the same.




回答3:


@Thomas Levesque: OK. So let's say that you can't extend JObject in Response<T> because you need to extend a pre-existing Response class. Here's another way you could implement the same solution:

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();
    }
}

So now there are the following options to Serialize the class:

   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));
    }

The Output will look something like this:

{"Int32":5}
{"String":"Message"}
{"Double":0.0}
{"DateTime":"2016-08-25T00:18:31.4882199-04:00"}


来源:https://stackoverflow.com/questions/39128650/how-to-get-the-name-of-t-from-generic-type-and-pass-it-into-jsonproperty

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!