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

前端 未结 3 1860
盖世英雄少女心
盖世英雄少女心 2020-12-06 10:40

I get the following error with the code below:

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

相关标签:
3条回答
  • 2020-12-06 11:22

    @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"}
    
    0 讨论(0)
  • 2020-12-06 11:39

    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.

    0 讨论(0)
  • 2020-12-06 11:40

    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
      }
    }
    
    0 讨论(0)
提交回复
热议问题