La meilleure façon de gérer cette situation est d'utiliser une coutume JsonConverter
.
Avant d'arriver au convertisseur, nous devons définir une classe dans laquelle désérialiser les données. Pour la Categories
propriété qui peut varier entre un élément unique et un tableau, définissez-la en tant que List<string>
et marquez-la avec un [JsonConverter]
attribut afin que JSON.Net sache qu'il doit utiliser le convertisseur personnalisé pour cette propriété. Je recommanderais également d'utiliser des [JsonProperty]
attributs afin que les propriétés des membres puissent recevoir des noms significatifs indépendamment de ce qui est défini dans le JSON.
class Item
{
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("timestamp")]
public int Timestamp { get; set; }
[JsonProperty("event")]
public string Event { get; set; }
[JsonProperty("category")]
[JsonConverter(typeof(SingleOrArrayConverter<string>))]
public List<string> Categories { get; set; }
}
Voici comment j'implémenterais le convertisseur. Notez que j'ai rendu le convertisseur générique afin qu'il puisse être utilisé avec des chaînes ou d'autres types d'objets selon les besoins.
class SingleOrArrayConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(List<T>));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
if (token.Type == JTokenType.Array)
{
return token.ToObject<List<T>>();
}
return new List<T> { token.ToObject<T>() };
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Voici un court programme démontrant le convertisseur en action avec vos exemples de données:
class Program
{
static void Main(string[] args)
{
string json = @"
[
{
""email"": ""[email protected]"",
""timestamp"": 1337966815,
""category"": [
""newuser"",
""transactional""
],
""event"": ""open""
},
{
""email"": ""[email protected]"",
""timestamp"": 1337966815,
""category"": ""olduser"",
""event"": ""open""
}
]";
List<Item> list = JsonConvert.DeserializeObject<List<Item>>(json);
foreach (Item obj in list)
{
Console.WriteLine("email: " + obj.Email);
Console.WriteLine("timestamp: " + obj.Timestamp);
Console.WriteLine("event: " + obj.Event);
Console.WriteLine("categories: " + string.Join(", ", obj.Categories));
Console.WriteLine();
}
}
}
Et enfin, voici la sortie de ce qui précède:
email: [email protected]
timestamp: 1337966815
event: open
categories: newuser, transactional
email: [email protected]
timestamp: 1337966815
event: open
categories: olduser
Violon: https://dotnetfiddle.net/lERrmu
ÉDITER
Si vous devez aller dans l'autre sens, c'est-à-dire sérialiser, tout en gardant le même format, vous pouvez implémenter la WriteJson()
méthode du convertisseur comme indiqué ci-dessous. (Assurez-vous de supprimer le CanWrite
remplacement ou de le modifier en retour true
, sinon WriteJson()
il ne sera jamais appelé.)
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
List<T> list = (List<T>)value;
if (list.Count == 1)
{
value = list[0];
}
serializer.Serialize(writer, value);
}
Violon: https://dotnetfiddle.net/XG3eRy
DeserializeObject
appel si vous utilisez l'[JsonConverter]
attribut sur la propriété de liste dans votre classe, comme indiqué dans la réponse ci-dessus. Si vous n'utilisez pas l'attribut, alors, oui, vous devrez passer le convertisseur àDeserializeObject
.List<T>
dans le convertisseurT[]
et le changement.Count
à.Length
. dotnetfiddle.net/vnCNgZJe travaillais là-dessus depuis des lustres, et merci à Brian pour sa réponse. Tout ce que j'ajoute, c'est la réponse vb.net!:
puis dans votre classe:
J'espère que cela vous fera gagner du temps
la source
En tant que variante mineure de la grande réponse de Brian Rogers , voici deux versions modifiées de
SingleOrArrayConverter<T>
.Tout d'abord, voici une version qui fonctionne pour tous
List<T>
pour chaque typeT
qui n'est pas lui-même une collection:Il peut être utilisé comme suit:
Remarques:
Le convertisseur évite d'avoir à pré-charger la valeur JSON entière en mémoire sous forme de
JToken
hiérarchie.Le convertisseur ne s'applique pas aux listes dont les éléments sont également sérialisés en tant que collections, par exemple
List<string []>
L'
canWrite
argument booléen passé au constructeur contrôle s'il faut re-sérialiser les listes à un seul élément en tant que valeurs JSON ou en tant que tableaux JSON.Le convertisseur
ReadJson()
utilise leexistingValue
if pré-alloué afin de prendre en charge le remplissage des membres de la liste en lecture seule.Deuxièmement, voici une version qui fonctionne avec d'autres collections génériques telles que
ObservableCollection<T>
:Ensuite, si votre modèle utilise, par exemple, un
ObservableCollection<T>
pour certainsT
, vous pouvez l'appliquer comme suit:Remarques:
SingleOrArrayListConverter
, leTCollection
type doit être en lecture / écriture et avoir un constructeur sans paramètre.Démo violon avec les tests unitaires de base ici .
la source
J'ai eu un problème très similaire. Ma requête Json était totalement inconnue pour moi. Je savais seulement.
Il y aura un objectId et des paires de valeurs clés anonymes ET des tableaux.
Je l'ai utilisé pour un modèle EAV que j'ai fait:
Ma requête JSON:
Ma classe j'ai défini:
et maintenant que je veux désérialiser des attributs inconnus avec sa valeur et ses tableaux, mon convertisseur ressemble à ça:
Alors maintenant, chaque fois que j'obtiens un AnonymObject, je peux parcourir le dictionnaire et à chaque fois qu'il y a mon drapeau "ValueDummyForEAV" je passe à la liste, lis la première ligne et divise les valeurs. Après cela, je supprime la première entrée de la liste et continue avec l'itération du dictionnaire.
Peut-être que quelqu'un a le même problème et peut l'utiliser :)
Cordialement Andre
la source
Vous pouvez utiliser un
JSONConverterAttribute
comme trouvé ici: http://james.newtonking.com/projects/json/help/En supposant que vous avez une classe qui ressemble à
Vous décoreriez la propriété de catégorie comme on le voit ici:
la source
Pour gérer cela, vous devez utiliser un JsonConverter personnalisé. Mais vous aviez probablement déjà cela à l'esprit. Vous recherchez simplement un convertisseur que vous pouvez utiliser immédiatement. Et cela offre plus qu'une simple solution à la situation décrite. Je donne un exemple avec la question posée.
Comment utiliser mon convertisseur:
Placez un attribut JsonConverter au-dessus de la propriété.
JsonConverter(typeof(SafeCollectionConverter))
Et voici mon convertisseur:
Et ce convertisseur utilise la classe suivante:
Que fait-il exactement? Si vous placez l'attribut convertisseur, le convertisseur sera utilisé pour cette propriété. Vous pouvez l'utiliser sur un objet normal si vous attendez un tableau json avec 1 ou aucun résultat. Ou vous l'utilisez sur un
IEnumerable
où vous attendez un objet json ou un tableau json. (Sachez qu'unarray
-object[]
- est unIEnumerable
) Un inconvénient est que ce convertisseur ne peut être placé qu'au-dessus d'une propriété car il pense pouvoir tout convertir. Et soyez prévenu . Astring
est également unIEnumerable
.Et il offre plus qu'une réponse à la question: si vous recherchez quelque chose par identifiant, vous savez que vous obtiendrez un tableau avec un ou aucun résultat. La
ToObjectCollectionSafe<TResult>()
méthode peut gérer cela pour vous.Ceci est utilisable pour Single Result vs Array à l'aide de JSON.net et gère à la fois un seul élément et un tableau pour la même propriété et peut convertir un tableau en un seul objet.
J'ai fait cela pour les demandes REST sur un serveur avec un filtre qui renvoyait un résultat dans un tableau mais je voulais récupérer le résultat sous la forme d'un seul objet dans mon code. Et aussi pour une réponse de résultat OData avec un résultat étendu avec un élément dans un tableau.
Aie du plaisir avec ça.
la source
J'ai trouvé une autre solution qui peut gérer la catégorie sous forme de chaîne ou de tableau en utilisant un objet. De cette façon, je n'ai pas besoin de gâcher le sérialiseur json.
Jetez-y un œil si vous avez le temps et dites-moi ce que vous en pensez. https://github.com/MarcelloCarreira/sendgrid-csharp-eventwebhook
Il est basé sur la solution à https://sendgrid.com/blog/tracking-email-using-azure-sendgrid-event-webhook-part-1/ mais j'ai également ajouté la conversion de date à partir de l'horodatage, mis à jour les variables pour refléter modèle SendGrid actuel (et fait fonctionner les catégories).
J'ai également créé un gestionnaire avec l'authentification de base en option. Voir les fichiers ashx et les exemples.
Je vous remercie!
la source