4

I'm trying to figure out how I can have a core object being returned from my API

public class Response<T> {
    public T Data {get;set;}
}

Where T is some object with properties e.g.

public class Thang {
   public string Thing  {get;set;}
}

Using JsonConvert.Serialize( myResponse ); will return the T Data property as Data, and rightly so.

But what If I wanted to use the name for the type of T? So the response Json would actually include a property called Thang not Data as follows.

{
    "thang": {
        "thing" : "hey"
    }
}

I'm curious if there is a relatively simple way to do this with Json.net or do you have to create a custom JsonConverter and use reflection to get the T type name when writing?

Thanks.

1 Answer 1

2

There's no built-in way to do this that I'm aware of.

You do need to use a little reflection, and you could probably use a custom JsonConverter, but you could also do it in just a few lines of code using a custom ContractResolver:

public class GenericPropertyContractResolver :
      CamelCasePropertyNamesContractResolver
{
    private readonly Type genericTypeDefinition;

    public GenericPropertyContractResolver(Type genericTypeDefinition)
    {
        this.genericTypeDefinition = genericTypeDefinition;
    }

    protected override JsonProperty CreateProperty(
        MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty baseProperty =
            base.CreateProperty(member, memberSerialization);

        Type declaringType = member.DeclaringType;

        if (!declaringType.IsGenericType ||
            declaringType.GetGenericTypeDefinition() != this.genericTypeDefinition)
        {
            return baseProperty;
        }

        Type declaringGenericType = declaringType.GetGenericArguments()[0];

        if (IsGenericMember(member))
        {
            baseProperty.PropertyName =
                this.ResolvePropertyName(declaringGenericType.Name);
        }

        return baseProperty;
    }

    // Is there a better way to do this? Determines if the member passed in
    // is a generic member in the open generic type.
    public bool IsGenericMember(MemberInfo member)
    {
        MemberInfo genericMember = 
            this.genericTypeDefinition.GetMember(member.Name)[0];

        if (genericMember != null)
        {
            if (genericMember.MemberType == MemberTypes.Field)
            {
                return ((FieldInfo)genericMember).FieldType.IsGenericParameter;
            }
            else if (genericMember.MemberType == MemberTypes.Property)
            {
                PropertyInfo property = (PropertyInfo)genericMember;

                return property
                    .GetMethod
                    .ReturnParameter
                    .ParameterType
                    .IsGenericParameter;
            }
        }

        return false;
    }
}

You could then use it like this:

var settings = new JsonSerializerSettings();
settings.ContractResolver = new GenericPropertyContractResolver(typeof(Response<>));

string serialized = JsonConvert.SerializeObject(new Response<Thang> 
{ 
    Data = new Thang { Thing = "Hey" }
}, settings);

Possibly a more straightforward thing to do would be to turn your class into a Dictionary before serializing it.

I also had a little trouble determining if a property on a closed generic type corresponded to a generic property on the open generic type--any tips on that would be appreciated.

Example: https://dotnetfiddle.net/DejOL2

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.