-
-
Save lucd/cdd57a2602bd975ec0a6 to your computer and use it in GitHub Desktop.
using Newtonsoft.Json; | |
using Newtonsoft.Json.Linq; | |
using System; | |
using System.Linq; | |
using System.Reflection; | |
namespace ConsoleApplication1 | |
{ | |
class Program | |
{ | |
/// <summary> | |
/// This code shows how to specify a navigation path in the JsonProperty attribute. | |
/// It will traverse the JSON node and obtain the desired value to according to the path. | |
/// Example | |
/// [JsonProperty(parent/child)] will map to the value "a" from the JSON { "parent":{ "child": "a"} }. | |
/// </summary> | |
/// <param name="args"></param> | |
static void Main(string[] args) | |
{ | |
string s = @" | |
{ | |
""id"": 100, | |
""fields"":{ | |
""issuetype"": { | |
""name"": ""I am a Jira issue"" | |
} | |
} | |
}"; | |
var settings = new JsonSerializerSettings(); | |
settings.Converters.Add(new ConventionBasedConverter()); | |
var o = JsonConvert.DeserializeObject<JiraIssue>(s, settings); | |
Console.WriteLine("Id: " + o.Id); | |
Console.WriteLine("Type: " + o.Type); | |
} | |
} | |
[JsonObject] | |
public class JiraIssue | |
{ | |
[JsonProperty("id")] | |
public int Id { get; set; } | |
[JsonProperty("fields/issuetype/name")] | |
public string Type { get; set; } | |
} | |
/// <summary> | |
/// Custom converter that allows mapping a JSON value according to a navigation path. | |
/// Credits to: http://stackoverflow.com/users/3887840/amit-kumar-ghosh | |
/// </summary> | |
class ConventionBasedConverter : JsonConverter | |
{ | |
public override bool CanConvert(Type objectType) | |
{ | |
return typeof(JiraIssue).IsAssignableFrom(objectType); | |
} | |
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | |
{ | |
var daat = JObject.Load(reader); | |
var issue = new JiraIssue(); | |
foreach (var prop in issue.GetType().GetProperties(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance)) | |
{ | |
var attr = prop.GetCustomAttributes(false).FirstOrDefault(); | |
if (attr != null) | |
{ | |
var propName = ((JsonPropertyAttribute)attr).PropertyName; | |
if (!string.IsNullOrWhiteSpace(propName)) | |
{ | |
//split by the delimiter, and traverse recursevly according to the path | |
var conventions = propName.Split('/'); | |
object propValue = null; | |
JToken token = null; | |
for (var i = 0; i < conventions.Length; i++) | |
{ | |
if (token == null) | |
{ | |
token = daat[conventions[i]]; | |
} | |
else { | |
token = token[conventions[i]]; | |
} | |
if (token == null) | |
{ | |
//silent fail: exit the loop if the specified path was not found | |
break; | |
} | |
else | |
{ | |
//store the current value | |
if (token is JValue) | |
{ | |
propValue = ((JValue)token).Value; | |
} | |
} | |
} | |
if (propValue != null) | |
{ | |
//workaround for numeric values being automatically created as Int64 (long) objects. | |
if (propValue is long && prop.PropertyType == typeof(Int32)) | |
{ | |
prop.SetValue(issue, Convert.ToInt32(propValue)); | |
} | |
else | |
{ | |
prop.SetValue(issue, propValue); | |
} | |
} | |
} | |
} | |
} | |
return issue; | |
} | |
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | |
{ | |
} | |
} | |
} |
@MinnowTman your solution is basically what I needed. Glad you already did a lot of the work for me :)
@MinnorTman Thanks for putting this together. It works fine for simple types, but doesn't work if the property is an object. For example, the following does not deserialize correctly - Person is null and WorkerId is not. If I don't use FlattenNestedJSONConverter, then Person is not null, but WorkerId is. Any thoughts on how to fix this? I'm happy to take a stab at it if you don't have the solution on the top of your head.
[JsonObject]
public class Worker
{
[JsonProperty(PropertyName = "workerId/idValue")]
public string WorkerId { get; set; }
[JsonProperty(PropertyName = "person")]
public Person Person { get; set; }
}
[JsonObject]
public class Person
{
[JsonProperty(PropertyName = "legalName")]
public string LegalName { get; set; }
}
SUPERBB!! Really helpful. Been search for solution yet this one is the best. Thank you!!
What about a dynamic attributes :-
{"root":{ "787<--This attribute name is dynamic-->":"Value" }.... }
What about a dynamic attributes :-
{"root":{ "787<--This attribute name is dynamic-->":"Value" }.... }
What do you mean with this?
Thank you for this. I spent 20 minutes looking at answers on SO that danced around this simple solution.
@MinnowTman - thank you! That gist really helped me out.
I've posted a solution with classes support here (based on @MinnowTman code)
https://stackoverflow.com/a/67932675/4601817
This version allows for the target class members to NOT have a JsonPropertyType attribute, and for the members to have other attributes besides just Json. Also tweaked CanConvert. I'm not sure what the original was up to. All I can say is that my version is what I needed. Made the target class a generic parameter. And I preferred a different name for the Converter class.