Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c#

json.net

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; }
}
like image 348
falcon Avatar asked Aug 24 '16 16:08

falcon


People also ask

How do I get the classname from generic type?

So you can do: A<String> a = new A<String>() {}; a now refers to a subclass of A<String> , so by getting a. getClass().

How do I get a class instance of generic type T?

The short answer is, that there is no way to find out the runtime type of generic type parameters in Java. A solution to this is to pass the Class of the type parameter into the constructor of the generic type, e.g.

What is the use of Jsonproperty in C#?

Compares the specified string to the name of this property. Provides a string representation of the property for debugging purposes. Writes the property to the provided writer as a named JSON object property.


1 Answers

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
  }
}
like image 198
Thomas Levesque Avatar answered Sep 28 '22 15:09

Thomas Levesque