Can I specify a path in an attribute to map a property in my class to a child property in my JSON?
有些代码(我无法更改)使用Newtonsoft.Json的
1 2 3 4 5 6 7 8 9 10 11 | { "picture": { "id": 123456, "data": { "type":"jpg", "url":"http://www.someplace.com/mypicture.jpg" } } } |
除了URL,我不在乎图片对象的其余部分,因此也不想在我的C#类中设置复杂的对象。 我真的只想要这样的东西:
1 2 | [DataMember(Name ="picture.data.url")] public string ProfilePicture { get; set; } |
这可能吗?
好吧,如果您只需要一个额外的属性,一种简单的方法是将JSON解析为
因此,假设您的课程如下所示:
1 2 3 4 5 6 7 8 9 10 | class Person { [JsonProperty("name")] public string Name { get; set; } [JsonProperty("age")] public string Age { get; set; } public string ProfilePicture { get; set; } } |
您可以这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | string json = @" { ""name"" :""Joe Shmoe"", ""age"" : 26, ""picture"": { ""id"": 123456, ""data"": { ""type"":""jpg"", ""url"":""http://www.someplace.com/mypicture.jpg"" } } }"; JObject jo = JObject.Parse(json); Person p = jo.ToObject<Person>(); p.ProfilePicture = (string)jo.SelectToken("picture.data.url"); |
小提琴:https://dotnetfiddle.net/7gnJCK
如果您希望使用更高级的解决方案,则可以创建一个自定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | class JsonPathConverter : JsonConverter { public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); object targetObj = Activator.CreateInstance(objectType); foreach (PropertyInfo prop in objectType.GetProperties() .Where(p => p.CanRead && p.CanWrite)) { JsonPropertyAttribute att = prop.GetCustomAttributes(true) .OfType<JsonPropertyAttribute>() .FirstOrDefault(); string jsonPath = (att != null ? att.PropertyName : prop.Name); JToken token = jo.SelectToken(jsonPath); if (token != null && token.Type != JTokenType.Null) { object value = token.ToObject(prop.PropertyType, serializer); prop.SetValue(targetObj, value, null); } } return targetObj; } public override bool CanConvert(Type objectType) { // CanConvert is not called when [JsonConverter] attribute is used return false; } public override bool CanWrite { get { return false; } } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } } |
为了演示,我们假设JSON现在如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | { "name":"Joe Shmoe", "age": 26, "picture": { "id": 123456, "data": { "type":"jpg", "url":"http://www.someplace.com/mypicture.jpg" } }, "favorites": { "movie": { "title":"The Godfather", "starring":"Marlon Brando", "year": 1972 }, "color":"purple" } } |
...除了您之前的信息外,您还对该人最喜欢的电影(标题和年份)和最喜欢的颜色感兴趣。您将首先使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | [JsonConverter(typeof(JsonPathConverter))] class Person { [JsonProperty("name")] public string Name { get; set; } [JsonProperty("age")] public int Age { get; set; } [JsonProperty("picture.data.url")] public string ProfilePicture { get; set; } [JsonProperty("favorites.movie")] public Movie FavoriteMovie { get; set; } [JsonProperty("favorites.color")] public string FavoriteColor { get; set; } } // Don't need to mark up these properties because they are covered by the // property paths in the Person class class Movie { public string Title { get; set; } public int Year { get; set; } } |
有了所有属性,您就可以照常反序列化了,它应该"正常工作":
1 | Person p = JsonConvert.DeserializeObject<Person>(json); |
小提琴:https://dotnetfiddle.net/Ljw32O
标记的答案不是100%完整,因为它忽略了可能会注册的任何IContractResolver,例如CamelCasePropertyNamesContractResolver等。
还返回false可以转换将防止其他情况,所以我将其更改为
这是更新的版本:
https://dotnetfiddle.net/F8C8U8
我也消除了在链接中说明的属性上设置
如果由于某种原因,以上链接消失或爆炸,我还包括以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | public class JsonPathConverter : JsonConverter { /// <inheritdoc /> public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); object targetObj = Activator.CreateInstance(objectType); foreach (PropertyInfo prop in objectType.GetProperties().Where(p => p.CanRead && p.CanWrite)) { JsonPropertyAttribute att = prop.GetCustomAttributes(true) .OfType<JsonPropertyAttribute>() .FirstOrDefault(); string jsonPath = att != null ? att.PropertyName : prop.Name; if (serializer.ContractResolver is DefaultContractResolver) { var resolver = (DefaultContractResolver)serializer.ContractResolver; jsonPath = resolver.GetResolvedPropertyName(jsonPath); } if (!Regex.IsMatch(jsonPath, @"^[a-zA-Z0-9_.-]+$")) { throw new InvalidOperationException($"JProperties of JsonPathConverter can have only letters, numbers, underscores, hiffens and dots but name was ${jsonPath}."); // Array operations not permitted } JToken token = jo.SelectToken(jsonPath); if (token != null && token.Type != JTokenType.Null) { object value = token.ToObject(prop.PropertyType, serializer); prop.SetValue(targetObj, value, null); } } return targetObj; } /// <inheritdoc /> public override bool CanConvert(Type objectType) { // CanConvert is not called when [JsonConverter] attribute is used return objectType.GetCustomAttributes(true).OfType<JsonPathConverter>().Any(); } /// <inheritdoc /> public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var properties = value.GetType().GetRuntimeProperties().Where(p => p.CanRead && p.CanWrite); JObject main = new JObject(); foreach (PropertyInfo prop in properties) { JsonPropertyAttribute att = prop.GetCustomAttributes(true) .OfType<JsonPropertyAttribute>() .FirstOrDefault(); string jsonPath = att != null ? att.PropertyName : prop.Name; if (serializer.ContractResolver is DefaultContractResolver) { var resolver = (DefaultContractResolver)serializer.ContractResolver; jsonPath = resolver.GetResolvedPropertyName(jsonPath); } var nesting = jsonPath.Split('.'); JObject lastLevel = main; for (int i = 0; i < nesting.Length; i++) { if (i == nesting.Length - 1) { lastLevel[nesting[i]] = new JValue(prop.GetValue(value)); } else { if (lastLevel[nesting[i]] == null) { lastLevel[nesting[i]] = new JObject(); } lastLevel = (JObject)lastLevel[nesting[i]]; } } } serializer.Serialize(writer, main); } } |
而不是做
1 |
你必须做
1 | lastLevel[nesting[i]] = JValue.FromObject(jValue); |
否则我们有一个
Could not determine JSON object type for type ...
例外
完整的代码片段是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | object jValue = prop.GetValue(value); if (prop.PropertyType.IsArray) { if(jValue != null) //https://stackoverflow.com/a/20769644/249895 lastLevel[nesting[i]] = JArray.FromObject(jValue); } else { if (prop.PropertyType.IsClass && prop.PropertyType != typeof(System.String)) { if (jValue != null) lastLevel[nesting[i]] = JValue.FromObject(jValue); } else { lastLevel[nesting[i]] = new JValue(jValue); } } |
如果有人需要同时使用@BrianRogers的JsonPathConverter和
删除
将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var properties = value.GetType().GetRuntimeProperties ().Where(p => p.CanRead && p.CanWrite); JObject main = new JObject (); foreach (PropertyInfo prop in properties) { JsonPropertyAttribute att = prop.GetCustomAttributes(true) .OfType<JsonPropertyAttribute>() .FirstOrDefault(); string jsonPath = (att != null ? att.PropertyName : prop.Name); var nesting=jsonPath.Split(new[] { '.' }); JObject lastLevel = main; for (int i = 0; i < nesting.Length; i++) { if (i == nesting.Length - 1) { lastLevel [nesting [i]] = new JValue(prop.GetValue (value)); } else { if (lastLevel [nesting [i]] == null) { lastLevel [nesting [i]] = new JObject (); } lastLevel = (JObject)lastLevel [nesting [i]]; } } } serializer.Serialize (writer, main); } |
正如我上面所说的,这仅适用于包含点的路径。鉴于此,您应该将以下代码添加到
1 2 3 4 5 6 7 | [...] string jsonPath = (att != null ? att.PropertyName : prop.Name); if (!Regex.IsMatch(jsonPath, @"^[a-zA-Z0-9_.-]+$")) { throw new InvalidOperationException("JProperties of JsonPathConverter can have only letters, numbers, underscores, hiffens and dots."); //Array operations not permitted } JToken token = jo.SelectToken(jsonPath); [...] |