Deserializzare il JSON in cui i valori sono nomi di campo con JSON.NET

Ho una situazione molto indesiderabile che mi richiede di deserializzare il JSON in cui i valori sono nomi di campo con JSON.NET. Supponendo che io abbia il seguente JSON che è strutturato molto correttamente:

{ "name": "tugberk", "roles": [ { "id": "1", "name": "admin" }, { "id": "2", "name": "guest" } ] } 

È molto facile deserializzare questo con JSON.NET su un object CLR:

 class Program { static void Main(string[] args) { var camelCaseSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }; var text = File.ReadAllText("user_normal.txt"); var obj = JsonConvert.DeserializeObject(text, camelCaseSettings); } } public class User { public string Name { get; set; } public Role[] Roles { get; set; } } public class Role { public int Id { get; set; } public string Name { get; set; } } 

Tuttavia, nel mio caso attuale, ho il seguente JSON orribile che è equivalente a JSON sopra in termini di valori:

 { "name": "tugberk", "roles": { "1": { "name": "admin" }, "2": { "name": "guest" } } } 

Come puoi vedere, il campo roles non è un array; è un object che contiene altri valori come oggetti con le sue chiavi univoche come i loro nomi di campo (che è orribile). Qual è il modo migliore per deserializzare questo JSON sopra la class User con JSON.NET?

È ansible creare un JsonConverter personalizzato che serializza / deserializza il Role[] . È quindi ansible decorare la proprietà dei Roles con JsonConverterAttribute questo modo:

 public class User { public string Name { get; set; } [JsonConverter(typeof(RolesConverter))] public Role[] Roles { get; set; } } 

Nella class del convertitore è ansible leggere un object e restituire una matrice. La tua class di convertitore potrebbe essere simile a questa:

 class RolesConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(Role[]); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // deserialize as object var roles = serializer.Deserialize(reader); var result = new List(); // create an array out of the properties foreach (JProperty property in roles.Properties()) { var role = property.Value.ToObject(); role.Id = int.Parse(property.Name); result.Add(role); } return result.ToArray(); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } } 

Ci sono alcune opzioni disponibili. Potresti avere un JsonConverter personalizzato e serializzarlo manualmente. Dal momento in cui scrivo questo fero ha fornito una risposta basata su questo, ti darò un’alternativa, che richiede due classi surrogate:

 public class JsonUser { public string Name { get; set; } public Dictionary Roles { get; set; } } public class JsonRole { public string Name { get; set; } } 

E nella tua class di Role :

 public static implicit operator User(JsonUser user) { return new User { Name = user.Name, Roles = user.Roles .Select(kvp => new Role { Id = kvp.Key, Name = kvp.Value.Name}) .ToArray() }; } 

Quale può essere usato in questo modo:

 User jsonUser = JsonConvert.DeserializeObject(json); 

Ora, questo viene fatto a spese della creazione di un object intermedio e probabilmente non è adatto per la maggior parte dei casi.

Per completezza, includerò la mia versione della soluzione JsonConverter:

 public class UserRolesConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof (Role[]); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { return serializer.Deserialize(reader) .Properties() .Select(p => new Role { Id = Int32.Parse(p.Name), Name = (string) p.Value["name"] }) .ToArray(); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } } public class User { public string Name { get; set; } [JsonConverter(typeof(UserRolesConverter))] public Role[] Roles { get; set; } } var jsonUser = JsonConvert.DeserializeObject(json); 

Hm puoi provare:

 dynamic jObject = JObject.Parse(text); List users = new List(); foreach(dynamic dUser in jObject) { List roles = new List(); User user = new User(); user.Name = dUser.name; foreach(PropertyInfo info in dUser.GetType().GetProperties()) { Role role = new Role(); role.Id = info.Name; role.Name = dUser[info.Name].name; roles.Ad(role); } user.Roles = roles.ToArray(); }