2

How would I get the JSON PropertyName of the following class & Property? Something like a "nameof()" equivilent for JSON Properties?

ie, something like

var jsonName = GetJSONPropertyName(SampleClass.SampleClassID); //should return "jsoniD"

public class SampleClass
{
    public SampleClass() { }
    [JsonProperty(PropertyName = "jsoniD")]
    public string SampleClassID { get; set; }
}
turkinator
  • 905
  • 9
  • 25
  • 3
    Not sure if Newtonsoft.Json (i assume that's the library you are using) has utility functions for this or not. But if not, you can always examine a type and its members through reflection to see whether they carry particular attributes and what the value of those attributes would be (see here: https://stackoverflow.com/questions/6637679/reflection-get-attribute-name-and-value-on-property) –  Feb 19 '19 at 20:54
  • 1
    @elgonzo describes how to do this. Realize that this is not like `nameof()`. The `nameof` keyword works at compile time (and has no runtime overhead), your `GetJsonPropertyName` function will run at runtime, invoking Reflection to do the work – Flydog57 Feb 19 '19 at 20:58
  • 1
    Duplicate or related: [Getting the JsonPropertyAttribute of a Property](https://stackoverflow.com/q/45826000) and [Get a list of JSON property names from a class to use in a query string](https://stackoverflow.com/q/33616005) and [Accessing value of JsonPropertyAttribute in C#](https://stackoverflow.com/q/53982500). – dbc Feb 20 '19 at 00:13

2 Answers2

4

A good question would be, how do you pass a property in a type-safe way. Properties are not first-class objects in .NET.

One of the ways would be this:

using System.Linq.Expressions;

// ...

static string GetJsonPropertyName<TC, TP>(Expression<Func<TC, TP>> expr)
{
    if (expr.Body is MemberExpression body)
        return body.Member.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName;
    else
        throw new ArgumentException("expect field access lambda");
}

You'll need to call the function like this:

var jsonName = GetJsonPropertyName<SampleClass, string>(x => x.SampleClassID);

Yes, it doesn't feel very natural. Sorry for that.


Thanks to @elgonzo, the code can be simplified like this:

static string GetJsonPropertyName<TC>(Expression<Func<TC, object>> expr)
{
    // in case the property type is a value type, the expression contains
    // an outer Convert, so we need to remove it
    var body = (expr.Body is UnaryExpression unary) ? unary.Operand : expr.Body;

    if (body is System.Linq.Expressions.MemberExpression memberEx)
        return memberEx.Member.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName;
    else
        throw new ArgumentException("expect field access lambda");
}

var jsonName = GetJsonPropertyName<SampleClass>(x => x.SampleClassID);
Vlad
  • 35,022
  • 6
  • 77
  • 199
  • 2
    I guess the signature of the method could be a little simplified to require only the type providing the property as generic type parameter by replacing TP with _object_: `static string GetJsonPropertyName(Expression> expr) { ... }`. Calling the method then would not require to know the property type: `GetJsonPropertyName(x => x.SampleClassID);` Not much, but a little things like these can sometimes go a long way... ;-) –  Feb 19 '19 at 21:38
  • 1
    @elgonzo: Indeed! I was mistakenly afraid that `x => x.SampleClassID` won't be convertable to `Func`. Thank you! – Vlad Feb 19 '19 at 21:58
  • 2
    Not sure if it's .NET Standard, the fact that my property is a Value Type, or something else, but my `expr.Body` is not a `MemberExpression`, it's a `UnaryExpression` with the `MemberExpression` in the Operand property. I've posted another reply with a .NET Standard solution based on your answer. – turkinator Feb 19 '19 at 22:51
1

Working Value-Type Expression support based on @Vlad's solution (with UnaryExpression pattern lifted from this SO POST)

public static string GetJsonPropertyName<T>(Expression<Func<T, object>> expr)
{
    if (((expr.Body as UnaryExpression)?.Operand ?? expr.Body) is MemberExpression body)
        return body.Member.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName;
    else
        throw new ArgumentException("expect field access lambda");
}
turkinator
  • 905
  • 9
  • 25
  • 2
    Oh I see: the problem is that in case of value-typed property we need additional boxing conversion of the property value to object. The first version of the code in my answer works well in this case too (but requires an additional generic parameter). – Vlad Feb 20 '19 at 00:28
  • @Vlad, my aplogies for making an improvement to your code that actually turned out to be incomplete/bad advice. I checked some of my own code i did some years ago for simplifying the creation of DependencyProperties based on evaluating lambda expressions to obtain the property name, and lo and behold, i actually also did check and handle UnaryExpressions there. I totally forgot about that. Ooops... –  Feb 20 '19 at 00:46
  • 1
    @elgonzo: Actually, I still see your advice as an improvement: the cost of additional check is negligible, and removal of an extra parameter is a good thing! Thank you! – Vlad Feb 20 '19 at 00:48