Désérialiser JSON en plusieurs propriétés

Je programme contre une API tierce qui renvoie des données JSON, mais le format peut être un peu étrange. Certaines propriétés peuvent être soit un object (qui contient une propriété Id), soit une chaîne (qui est l’ID de l’object). Par exemple, les deux éléments suivants sont valides:

{ ChildObject: 'childobjectkey1' } 

et

 { ChildObject: { Id: 'childobjectkey1', // (other properties) } } 

J’essaie de désérialiser cela en utilisant JSON.net dans une classe fortement typée, mais je n’ai pas eu beaucoup de chance jusqu’à présent. Ma meilleure idée était de le sérialiser en deux propriétés, une chaîne et l’autre object, et d’utiliser un JsonConverter personnalisé pour chacune d’elles afin de permettre le comportement variable:

 public abstract class BaseEntity { public ssortingng Id { get; set; } } public class ChildObject : BaseEntity { } public class MyObject { [JsonProperty("ChildObject")] [JsonConverter(typeof(MyCustomIdConverter))] public ssortingng ChildObjectId { get; set; } [JsonProperty("ChildObject")] [JsonConverter(typeof(MyCustomObjectConverter))] public ChildObject ChildObject { get; set; } } 

Toutefois, la définition de l’atsortingbut JsonProperty sur deux propriétés avec le même PropertyName provoque l’exception:

Newtonsoft.Json.JsonSerializationException: un membre portant le nom ‘ChildObject’ existe déjà sur ‘…..’. Utilisez le JsonPropertyAtsortingbute pour spécifier un autre nom.

Je suis à peu près sûr que l’approche JsonConverter fonctionnera si je peux surmonter cet obstacle – je suppose que l’erreur existe, car l’atsortingbut JsonProperty est utilisé pour la sérialisation et la désérialisation. Dans ce cas, je n’ai aucun intérêt à sérialiser cette classe – elle ne sera jamais utilisée que comme cible pour la désérialisation.

Je n’ai aucun contrôle sur l’extrémité distante (c’est une API tierce), mais j’aimerais pouvoir réaliser cette sérialisation. Cela ne me dérange pas si elle utilise l’approche que j’ai commencée ou à laquelle je n’ai pas encore pensé.

Cette question est également liée, mais il n’y avait pas de réponse.

Essayez ceci (étendez-le avec une validation approfondie si vous l’utiliserez dans votre code):

 public class MyObject { public ChildObject MyChildObject; public ssortingng MyChildObjectId; [JsonProperty("ChildObject")] public object ChildObject { get { return MyChildObject; } set { if (value is JObject) { MyChildObject = ((JToken)value).ToObject(); MyChildObjectId = MyChildObject.Id; } else { MyChildObjectId = value.ToSsortingng(); MyChildObject = null; } } } } 

Plutôt que de créer deux convertisseurs distincts pour chacun des champs, il serait sage de créer un seul convertisseur pour la propriété “main” et de lier l’autre à celle-ci. ChildObjectId est dérivé de ChildObject .

 public class MyObject { [JsonIgnore] public ssortingng ChildObjectId { get { return ChildObject.Id; } // I would advise against having a setter here // you should only allow changes through the object only set { ChildObject.Id = value; } } [JsonConverter(typeof(MyObjectChildObjectConverter))] public ChildObject ChildObject { get; set; } } 

Maintenant, convertir ChildObject peut être un peu un défi. Il existe deux représentations possibles de l’object: une chaîne ou un object. Déterminez quelle représentation vous avez et effectuez la conversion.

 public class MyObjectChildObjectConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType == typeof(ChildObject); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { serializer.Serialize(writer, value); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var obj = serializer.Deserialize(reader); switch (obj.Type) { case JTokenType.Object: return ReadAsObject(obj as JObject); case JTokenType.Ssortingng: return ReadAsSsortingng((ssortingng)(JValue)obj); default: throw new JsonSerializationException("Unexpected token type"); } } private object ReadAsObject(JObject obj) { return obj.ToObject(); } private object ReadAsSsortingng(ssortingng str) { // do a lookup for the actual object or whatever here return new ChildObject { Id = str, }; } } 

Voici ce que je ferais dans cette situation.

  • N’avoir qu’une seule propriété dans la classe parent pour l’object enfant et la ChildObject type ChildObject
  • Créez un JsonConverter personnalisé qui peut inspecter le JSON et soit:
    • désérialiser l’instance complète de l’object enfant si les données sont présentes, ou
    • créez une nouvelle instance de l’object enfant et définissez son ID, en laissant toutes les autres propriétés vides. (Vous pouvez également faire ce que Jeff Mercado a suggéré et demander au convertisseur de charger l’object à partir d’une firebase database basée sur l’ID, si cela s’applique à votre situation.)
  • Vous pouvez éventuellement définir une propriété sur l’object enfant pour indiquer s’il est entièrement rempli. Le convertisseur peut définir cette propriété lors de la désérialisation.

Après la désérialisation, s’il y avait une propriété ChildObject dans le JSON (avec un ID ou une valeur d’object complet), vous avez la garantie d’avoir une instance ChildObject et vous pouvez obtenir son ID; sinon, s’il n’y avait pas de propriété ChildObject dans le JSON, la propriété ChildObject de la classe parent serait null.

Vous trouverez ci-dessous un exemple de travail complet à démontrer. Dans cet exemple, j’ai modifié la classe parente afin d’inclure trois instances distinctes de ChildObject afin d’afficher les différentes possibilités offertes par le JSON (ID de chaîne uniquement, object complet et aucune présente). Ils utilisent tous le même convertisseur. J’ai également ajouté une propriété Name et une propriété IsFullyPopulated à la classe ChildObject .

Voici les cours DTO:

 public abstract class BaseEntity { public ssortingng Id { get; set; } } public class ChildObject : BaseEntity { public ssortingng Name { get; set; } public bool IsFullyPopulated { get; set; } } public class MyObject { [JsonProperty("ChildObject1")] [JsonConverter(typeof(MyCustomObjectConverter))] public ChildObject ChildObject1 { get; set; } [JsonProperty("ChildObject2")] [JsonConverter(typeof(MyCustomObjectConverter))] public ChildObject ChildObject2 { get; set; } [JsonProperty("ChildObject3")] [JsonConverter(typeof(MyCustomObjectConverter))] public ChildObject ChildObject3 { get; set; } } 

Voici le convertisseur:

 class MyCustomObjectConverter : JsonConverter { public override bool CanConvert(Type objectType) { return (objectType == typeof(ChildObject)); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JToken token = JToken.Load(reader); ChildObject child = null; if (token.Type == JTokenType.Ssortingng) { child = new ChildObject(); child.Id = token.ToSsortingng(); child.IsFullyPopulated = false; } else if (token.Type == JTokenType.Object) { child = token.ToObject(); child.IsFullyPopulated = true; } else if (token.Type != JTokenType.Null) { throw new JsonSerializationException("Unexpected token: " + token.Type); } return child; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { serializer.Serialize(writer, value); } } 

Voici le programme de test pour démontrer le fonctionnement du convertisseur:

 class Program { static void Main(ssortingng[] args) { ssortingng json = @" { ""ChildObject1"": { ""Id"": ""key1"", ""Name"": ""Foo Bar Baz"" }, ""ChildObject2"": ""key2"" }"; MyObject obj = JsonConvert.DeserializeObject(json); DumpChildObject("ChildObject1", obj.ChildObject1); DumpChildObject("ChildObject2", obj.ChildObject2); DumpChildObject("ChildObject3", obj.ChildObject3); } static void DumpChildObject(ssortingng prop, ChildObject obj) { Console.WriteLine(prop); if (obj != null) { Console.WriteLine(" Id: " + obj.Id); Console.WriteLine(" Name: " + obj.Name); Console.WriteLine(" IsFullyPopulated: " + obj.IsFullyPopulated); } else { Console.WriteLine(" (null)"); } Console.WriteLine(); } } 

Et voici le résultat de ce qui précède:

 ChildObject1 Id: key1 Name: Foo Bar Baz IsFullyPopulated: True ChildObject2 Id: key2 Name: IsFullyPopulated: False ChildObject3 (null)