Désérialisation d'un tableau d'objets JSON avec Json.net

118

J'essaie d'utiliser une API qui utilise la structure d'exemple suivante pour leur json retourné

[
   {
      "customer":{
         "first_name":"Test",
         "last_name":"Account",
         "email":"[email protected]",
         "organization":"",
         "reference":null,
         "id":3545134,
         "created_at":"2013-08-06T15:51:15-04:00",
         "updated_at":"2013-08-06T15:51:15-04:00",
         "address":"",
         "address_2":"",
         "city":"",
         "state":"",
         "zip":"",
         "country":"",
         "phone":""
      }
   },
   {
      "customer":{
         "first_name":"Test",
         "last_name":"Account2",
         "email":"[email protected]",
         "organization":"",
         "reference":null,
         "id":3570462,
         "created_at":"2013-08-12T11:54:58-04:00",
         "updated_at":"2013-08-12T11:54:58-04:00",
         "address":"",
         "address_2":"",
         "city":"",
         "state":"",
         "zip":"",
         "country":"",
         "phone":""
      }
   }
]

JSON.net fonctionnerait très bien avec quelque chose comme la structure suivante

{
    "customer": {
        ["field1" : "value", etc...],
        ["field1" : "value", etc...],
    }
}

Mais je ne peux pas comprendre comment le rendre satisfait de la structure fournie.

L'utilisation du JsonConvert.DeserializeObject (content) par défaut donne le nombre correct de Customer, mais toutes les données sont nulles.

Faire quelque chose dans CustomerList (ci-dessous) entraîne une exception «Impossible de désérialiser le tableau JSON actuel»

public class CustomerList
{
    public List<Customer> customer { get; set; }
}

Pensées?

Shawn C.
la source
Est-ce que cela répond à votre question? Désérialiser JSON avec C #
GetFookedWeeb

Réponses:

187

Vous pouvez créer un nouveau modèle pour désérialiser votre Json CustomerJson:

public class CustomerJson
{
    [JsonProperty("customer")]
    public Customer Customer { get; set; }
}

public class Customer
{
    [JsonProperty("first_name")]
    public string Firstname { get; set; }

    [JsonProperty("last_name")]
    public string Lastname { get; set; }

    ...
}

Et vous pouvez désérialiser votre json facilement:

JsonConvert.DeserializeObject<List<CustomerJson>>(json);

J'espère que ça aide !

Documentation: sérialisation et désérialisation de JSON

Joffrey Kern
la source
1
Merci. Était en train de réfléchir au problème. Comme vous avez répondu d'abord, votre réponse a été acceptée.
Shawn C.
2
JsonConvert.DeserializeObject <Liste <CustomerJson>> (json); Fonctionne parfaitement pour les entrées de chaîne.
Markel Mairs
DeserializeObject()est lent sur les téléphones Android exécutant ARM. Une meilleure solution pour ce cas?
Tadej
1
Essayez de naviguer avec un JObjectJObject.Parse(json);
Joffrey Kern
47

Pour ceux qui ne souhaitent pas créer de modèles, utilisez le code suivant:

var result = JsonConvert.DeserializeObject<
  List<Dictionary<string, 
    Dictionary<string, string>>>>(content);

Remarque: cela ne fonctionne pas pour votre chaîne JSON. Ce n'est pas une solution générale pour aucune structure JSON.

Tyler Long
la source
10
C'est une solution terrible. Au lieu de cela, si vous ne voulez pas créer de modèles, utilisezvar result = JsonConvert.DeserializeObject<Tuple<string, string, string>>(content);
a11smiles
1
@ a11smiles Veuillez expliquer pourquoi c'est une solution terrible.
Tyler Long
2
Tout d'abord, une allocation mémoire inutile pour les différents types d' IEnumerableimplémentations (3 par rapport à un List <Tuple>). Deuxièmement, votre solution implique deux clés distinctes - 1 pour chaque dictionnaire. Que se passe-t-il si plusieurs clients portent le même prénom? Il n'y aurait pas de différenciation sur les clés. Votre solution ne prend pas ce conflit en considération.
a11 sourit
2
@ a11smiles chaque client est un dictionnaire distinct. Il n'y aura donc aucun problème, même s'il y a plusieurs clients ayant le même prénom.
Tyler Long
1
@ a11smiles Je me demande pourquoi pensez-vous que var result = JsonConvert.DeserializeObject<Tuple<string, string, string>>(content);cela fonctionnerait. Apparemment, cela ne fonctionne pas
Tyler Long
1

En utilisant la réponse acceptée, vous devez accéder à chaque enregistrement en utilisant Customers[i].customer, et vous avez besoin d'une CustomerJsonclasse supplémentaire , ce qui est un peu ennuyeux. Si vous ne souhaitez pas faire cela, vous pouvez utiliser ce qui suit:

public class CustomerList
{
    [JsonConverter(typeof(MyListConverter))]
    public List<Customer> customer { get; set; }
}

Notez que j'utilise un List<>, pas un tableau. Créez maintenant la classe suivante:

class MyListConverter : JsonConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var token = JToken.Load(reader);
        var list = Activator.CreateInstance(objectType) as System.Collections.IList;
        var itemType = objectType.GenericTypeArguments[0];
        foreach (var child in token.Values())
        {
            var childToken = child.Children().First();
            var newObject = Activator.CreateInstance(itemType);
            serializer.Populate(childToken.CreateReader(), newObject);
            list.Add(newObject);
        }
        return list;
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType.IsGenericType && (objectType.GetGenericTypeDefinition() == typeof(List<>));
    }
    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}
AlexDev
la source
1

Légère modification de ce qui a été indiqué ci-dessus. Mon format Json, qui valide était

{
    mycollection:{[
           {   
               property0:value,
               property1:value,
             },
             {   
               property0:value,
               property1:value,
             }
           ]

         }
       }

En utilisant la réponse d'AlexDev, j'ai fait cette boucle chaque enfant, en créant un lecteur à partir de celui-ci

 public partial class myModel
{
    public static List<myModel> FromJson(string json) => JsonConvert.DeserializeObject<myModelList>(json, Converter.Settings).model;
}

 public class myModelList {
    [JsonConverter(typeof(myModelConverter))]
    public List<myModel> model { get; set; }

}

class myModelConverter : JsonConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var token = JToken.Load(reader);
        var list = Activator.CreateInstance(objectType) as System.Collections.IList;
        var itemType = objectType.GenericTypeArguments[0];
        foreach (var child in token.Children())  //mod here
        {
            var newObject = Activator.CreateInstance(itemType);
            serializer.Populate(child.CreateReader(), newObject); //mod here
            list.Add(newObject);
        }
        return list;
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType.IsGenericType && (objectType.GetGenericTypeDefinition() == typeof(List<>));
    }
    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();

}
JC_VA
la source
0

Nouvelle modification de JC_VA, prenez ce qu'il a et remplacez le MyModelConverter par ...

public class MyModelConverter : JsonConverter
{
    //objectType is the type as specified for List<myModel> (i.e. myModel)
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var token = JToken.Load(reader); //json from myModelList > model
        var list = Activator.CreateInstance(objectType) as System.Collections.IList; // new list to return
        var itemType = objectType.GenericTypeArguments[0]; // type of the list (myModel)
        if (token.Type.ToString() == "Object") //Object
        {
            var child = token.Children();
            var newObject = Activator.CreateInstance(itemType);
            serializer.Populate(token.CreateReader(), newObject);
            list.Add(newObject);
        }
        else //Array
        {
            foreach (var child in token.Children())
            {
                var newObject = Activator.CreateInstance(itemType);
                serializer.Populate(child.CreateReader(), newObject);
                list.Add(newObject);
            }
        }
        return list;

    }

    public override bool CanConvert(Type objectType)
    {
        return objectType.IsGenericType && (objectType.GetGenericTypeDefinition() == typeof(List<>));
    }
    public override bool CanWrite => false;
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException();
}

Cela devrait fonctionner pour json qui est soit

myModelList{
 model: [{ ... object ... }]
}

ou

myModelList{
 model: { ... object ... }
}

ils finiront tous les deux par être analysés comme s'ils étaient

myModelList{
 model: [{ ... object ... }]
}
andmar8
la source