我可以在属性中指定path来将我的类中的属性映射到我的JSON中的子属性?
有一些代码(我不能改变),它使用Newtonsoft.Json的DeserializeObject<T>(strJSONData)
从Web请求中获取数据并将其转换为类对象(我可以更改类)。 通过使用[DataMember(Name = "raw_property_name")]
装饰我的类属性,我可以将原始JSON数据映射到我的类中正确的属性。 有没有一种方法可以将JSON复杂对象的子属性映射到一个简单的属性? 这是一个例子:
{ "picture": { "id": 123456, "data": { "type": "jpg", "url": "http://www.someplace.com/mypicture.jpg" } } }
我不关心其他的图片对象,除了URL,所以不想在我的C#类中设置一个复杂的对象。 我真的只想要像这样的东西:
[DataMember(Name = "picture.data.url")] public string ProfilePicture { get; set; }
这可能吗?
那么,如果你只需要一个额外的属性,一个简单的方法就是将你的JSONparsing成一个JObject
,使用ToObject()
从JObject
填充你的类,然后使用SelectToken()
来获取额外的属性。
所以,假设你的class级看起来像这样:
class Person { [JsonProperty("name")] public string Name { get; set; } [JsonProperty("age")] public string Age { get; set; } public string ProfilePicture { get; set; } }
你可以这样做:
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
如果你喜欢更漂亮的解决scheme,你可以做一个自定义的JsonConverter
来启用JsonProperty
属性的行为就像你描述的那样。 转换器需要在课堂上进行操作,并结合上述技术使用一些reflection来填充所有的属性。 以下是代码中的样子:
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现在看起来如下所示:
{ "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" } }
…除了之前的信息之外,您还对该人最喜欢的电影(标题和年份)以及喜欢的颜色感兴趣。 您将首先用[JsonConverter]
属性标记您的目标类以将其与自定义转换器相关联,然后在每个属性上使用[JsonProperty]
属性,指定所需的属性path(区分大小写)作为名称。 目标属性不一定是原语,你可以像使用Movie
一样使用子类(并注意到不需要插入Favorites
类)。
[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; } }
有了所有的属性,你可以正常反序列化,它应该“只是工作”:
Person p = JsonConvert.DeserializeObject<Person>(json);
小提琴: https : //dotnetfiddle.net/Ljw32O
如果有人需要使用@BrianRogers的JsonPathConverter和WriteJson
选项,下面是一个解决scheme(仅适用于只有WriteJson
的path):
删除CanWrite
属性,使其默认为true
。
用WriteJson
代码replaceWriteJson
代码:
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); }
正如我上面所说,这只适用于包含点的path。 鉴于此,您应该将以下代码添加到ReadJson
以防止其他情况:
[...] 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); [...]
标记的答案不是100%完整的,因为它忽略了可能被注册的任何IContractResolver,如CamelCasePropertyNamesContractResolver等。
也返回false可以转换将阻止其他用户的情况下,所以我改变它return objectType.GetCustomAttributes(true).OfType<JsonPathConverter>().Any();
这是更新的版本: https : //dotnetfiddle.net/F8C8U8
我也删除了需要设置属性的JsonProperty
,如链接中所示。
如果由于某种原因,上面的链接死亡或爆炸,我还包括下面的代码:
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); } }