如何从泛型类型中获取<T>的名称并将其传递给JsonProperty()?

14

我使用以下代码时出现以下错误:

"需要对象引用才能访问非静态字段、方法或属性 'Response.PropName'"

代码:

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

1
属性需要常量值。 - Daniel A. White
1
@DanielA.White 更准确地说,是常量值。 - Ivan Stoev
1
这是行不通的;属性需要常量值,而你的 PropName 属性只在运行时评估。 - Thomas Levesque
我想知道这里是否有纯粹的JSON.NET解决方案或者一些扩展可以提高其灵活性。 - Daniel A. White
3个回答

9
你想要做的事情是可能的,但不是一件简单的事情,并且不能仅使用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代表在Response<T>的泛型类型参数中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
  }
}

很棒的答案!对于在 MyClass<T> 中包含 List<T> 也适用... 在我的情况下,我不想依赖于 T 的名称(以防有人决定重命名它),而是在 T 中使用了常量字段:prop.PropertyName = typeArgs[0].GetField("nameInJson").GetRawConstantValue().ToString(); - Do-do-new

3
这是一种可能更简单的实现方式。您需要做的就是让 Response 扩展 JObject,就像这样:
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。这应该能够正常工作。


这是一个创意解决方案,但“你所需要做的就是让Response扩展JObject”是一个相当大的要求... - Thomas Levesque
如果 Response 无法被扩展,他可以使用组合并创建一个类似的 Container<T> 类,该类将成为 Response<T> 的属性。稍后我会尝试添加一个示例... - Milton Hernandez

0

@Thomas Levesque:好的。假设您无法在Response<T>中扩展JObject,因为您需要扩展一个预先存在的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"}

如果我只有一个未知参数和其他已知参数怎么办?例如:public class ReturnResponse<T> {
[JsonProperty("status")] public Result status { get; set; } private static string TypeName = (typeof(T)).Name; private T _data; public T Data { get { return _data; } set { _data = value; this[TypeName] = JToken.FromObject(_data); } } } - 仅返回通用数据并忽略状态参数。
- aura

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接