Partager via


JSON

Analyser des chaînes JSON dans les composants Windows Runtime

Craig Shoemaker

Télécharger l'exemple de code

Certes, les applications du Windows store réalisées avec JavaScript permettent à toute personne disposant de compétences en HTML et JavaScript de créer des applications Windows natives, mais JavaScript n'est pas toujours la meilleure solution pour résoudre tous les problèmes. Certains comportements de vos applications peuvent bénéficier d'une meilleure implémentation si vous optez pour une méthode plus orientée objet utilisant C#, Visual Basic ou C++. En outre, certains aspects de votre code seront susceptibles d'être réutilisés parmi les divers composants Windows Runtime (WinRT) qui ont besoin de données provenant de la couche d'interface utilisateur. Dans l'une ou l'autre de ces situations, il est important de comprendre la transmission des données du JavaScript vers les composants WinRT, puis vers l'interface utilisateur.

Sur le Web, les données sont souvent transmises du client vers le serveur, puis du serveur vers le client sous forme d'objets JSON. Dans différents contextes, des infrastructures comme Web Forms ASP.NET et MVC ASP.NET comprennent des fonctionnalités telles que les classeurs de modèles ou tout au moins une forme de gestion « auto-magique » côté serveur pour analyser les objets JSON. Les composants WinRT ont des objets permettant d'analyser JSON, mais le niveau de prise en charge est faible et une interaction plus optimisée requiert une gestion explicite de votre part.

Cet article explique comment analyser de manière fiable des chaînes JSON transmises aux composants WinRT afin d'alimenter les objets fortement typés et de retourner un résultat vers l'interface utilisateur.

Restrictions d'interaction avec les composants WinRT

Avant d'aborder les spécificités de l'analyse des objets JSON, vous devez commencer par vous familiariser avec les exigences et les restrictions associées à l'interaction avec les composants WinRT. La rubrique d'aide MSDN « Création de composants Windows Runtime en C# et Visual Basic » (bit.ly/WgBBai) détaille les éléments nécessaires aux composants WinRT en ce qui concerne la déclaration du paramètre de méthode et des types de retour. Les types autorisés se composent largement de types primitifs et de quelques types de collection. C'est la raison pour laquelle il n'est pas permis de passer un objet JSON brut dans un composant. La meilleure solution pour passer un objet JSON dans un composant managé consiste d'abord à sérialiser l'objet JSON (à l'aide de la méthode JSON.stringify). En effet, les chaînes sont totalement prises en charge dans ces classes.

Analyse des objets JSON dans du code managé

L'espace de noms Windows.Data.Json comprend un certain nombre de classes différentes conçues de façon à pouvoir être utilisées avec les objets JSON de façon fortement typée. Il s'agit notamment des classes JsonValue, JsonArray et JsonObject. La classe JsonValue représente une valeur JSON exposée sous forme de chaîne, de nombre, de booléen, de tableau ou d'objet (pour en savoir plus, consultez bit.ly/14AcTmF). Pour analyser une chaîne JSON, vous devez passer la chaîne brute à JsonValue, qui peut ensuite retourner une instance de JsonObject.

La classe JsonObject représente un objet JSON complet et comprend des méthodes permettant de manipuler l'objet source. Via la classe JsonObject, vous pouvez ajouter ou supprimer des membres, extraire des données des membres, itérer sur chaque membre, voire sérialiser à nouveau l'objet. Pour plus de détails sur JsonObject, consultez bit.ly/WDWZkG.

La classe JsonArray représente un tableau JSON qui, à nouveau, comprend une multitude de méthodes permettant de contrôler le tableau, par exemple l'itération, l'ajout et la suppression d'éléments du tableau. Pour plus d'informations sur l'interface de la classe JsonArray, consultez bit.ly/XVUZo1.

Pour découvrir comment vous pouvez commencer à utiliser ces classes, observez l'exemple d'objet JSON suivant en JavaScript :

{
  firstName: "Craig"
}

Avant de chercher à passer cet objet dans un composant WinRT, vous devez sérialiser l'objet en chaîne à l'aide de la fonction JSON.stringify. J'attire votre attention sur ce qui se passe après la sérialisation de l'objet ; le même objet est représenté de la façon suivante :

"{
  '_backingData': {
    'firstName': 'Craig'
  },
  'firstName': 'Craig',
  'backingData': {
    'firstName':'Craig'}
}"

Cela vous surprendra peut-être puisque le même appel de fonction dans un navigateur Web sérialise simplement l'objet dans une chaîne sans ajouter aucun membre à l'objet. Ce changement de structure de la chaîne JSON a un impact sur la façon dont vous récupérez les données de l'objet.

La première étape du processus de lecture de ces données dans un composant WinRT consiste à tenter d'analyser la chaîne entrante comme instance de JsonValue. Si l'analyse est réussie, vous pouvez ensuite demander JsonObject depuis l'instance racine de JsonValue. Dans ce cas, JsonValue est l'objet racine tel qu'il a été créé par l'appel de la fonction stringify et JsonObject vous donne accès à l'objet d'origine avec lequel vous avez commencé en JavaScript.

Le code suivant décrit la façon dont vous pouvez utiliser la méthode GetNamedString pour extraire la valeur du membre « firstName » et l'insérer dans une variable, une fois JsonObject disponible :

JsonValue root;
JsonObject jsonObject;
string firstName;
if (JsonValue.TryParse(jsonString, out root))
{
  jsonObject = root.GetObject();
  if (jsonObject.ContainsKey("firstName"))
  {
    firstName = jsonObject.GetNamedString("firstName");
  }
}

Une approche similaire est utilisée pour accéder aux membres booléens et numériques, lorsque les méthodes GetNamedBoolean et GetNamedNumber sont disponibles. L'étape suivante consiste à implémenter les méthodes d'extension de JsonObject afin de faciliter l'accès aux données JSON.

Méthodes d'extension de JsonObject

L'implémentation par défaut de la classe JsonObject apporte un comportement de niveau inférieur largement amélioré par des méthodes simples capables de gérer les mises en forme imparfaites et d'éviter les exceptions si les membres n'existent pas dans la source. En d'autres termes, les objets créés en JavaScript rencontrent inévitablement des problèmes de structure ou de mise en forme qui risquent d'entraîner des exceptions. L'ajout des méthodes d'extension suivantes à la classe JsonObject permet de réduire le nombre de ces problèmes.

La première méthode d'extension à ajouter se nomme GetStringValue. La figure 1 illustre l'implémentation, qui commence par la vérification de l'existence du membre sur l'objet. Dans ce cadre, le paramètre clé est le nom de la propriété de l'objet JSON. Une fois que l'existence du membre a été confirmée, la méthode TryGetValue permet de tenter d'accéder aux données depuis l'instance de JsonObject. Si la valeur est trouvée, elle est retournée comme objet implémentant l'interface IJsonValue.

Figure 1 Implémentation de la méthode d'extension GetStringValue

public static string GetStringValue(this JsonObject jsonObject, 
  string key)
{
  IJsonValue value;
  string returnValue = string.Empty;
  if (jsonObject.ContainsKey(key))
  {
    if (jsonObject.TryGetValue(key, out value))
    {
      if (value.ValueType == JsonValueType.String)
      {
        returnValue = jsonObject.GetNamedString(key);
      }
      else if (value.ValueType == JsonValueType.Number)
      {
        returnValue = jsonObject.GetNamedNumber(key).ToString();
      }
      else if (value.ValueType == JsonValueType.Boolean)
      {
        returnValue = jsonObject.GetNamedBoolean(key).ToString();
      }
    }
  }
  return returnValue;
}

L'interface IJsonValue comprend la propriété ValueType en lecture seule qui expose la valeur donnée de l'énumération JsonValueType indiquant le type de données de l'objet. Une fois ValueType interrogé, la méthode correctement typée est utilisée afin d'extraire les données de l'objet.

La méthode GetStringValue comprend la reconnaissance des valeurs booléennes et numériques afin d'assurer la protection contre les objets JSON mal formés. Vous pouvez choisir de rendre votre implémentation plus stricte et renoncer à l'analyse ou générer une erreur si l'objet JSON n'est pas mis en forme comme il le devrait pour le type attendu, mais le code présenté ici rend l'opération d'analyse plus souple et protège contre les erreurs.

La méthode d'extension suivante, présentée à la figure 2, correspond à l'implémentation permettant d'extraire les valeurs booléennes. Dans le cas présent, les valeurs booléennes exprimées sous forme de chaînes (par exemple, « 1 » ou « true » pour une valeur true, etc.) et de numéros (par exemple, « 1 » pour true et « 0 » pour false) sont prises en charge dans la méthode GetBooleanValue.

Figure 2 Implémentation de la méthode d'extension GetBooleanValue

public static bool? GetBooleanValue(this JsonObject jsonObject, 
  string key)
{
  IJsonValue value;
  bool? returnValue = null;
  if (jsonObject.ContainsKey(key))
  {
    if (jsonObject.TryGetValue(key, out value))
    {
      if (value.ValueType == JsonValueType.String)
      {
        string v = jsonObject.GetNamedString(key).ToLower();
        if (v == "1" || v == "true")
        {
          returnValue = true;
        }
        else if (v == "0" || v == "false")
        {
          returnValue = false;
        }
      }
      else if (value.ValueType == JsonValueType.Number)
      {
        int v = Convert.ToInt32(jsonObject.GetNamedNumber(key));
        if (v == 1)
        {
          returnValue = true;
        }
        else if (v == 0)
        {
          returnValue = false;
        }
      }
      else if (value.ValueType == JsonValueType.Boolean)
      {
        returnValue = value.GetBoolean();
      }
    }
  }
  return returnValue;
}

Les méthodes d'extension reposant sur des nombres sont définies de façon à retourner des types Nullable. Par conséquent, dans le cas présent, GetDoubleValue retourne un double Nullable. Ici, le comportement correcteur tente de convertir les chaînes en valeurs numériques correspondantes possibles (voir la figure 3).

Figure 3 Implémentation de la méthode d'extension GetDoubleValue

public static double? GetDoubleValue(this JsonObject jsonObject, 
  string key)
{
  IJsonValue value;
  double? returnValue = null;
  double parsedValue;
  if (jsonObject.ContainsKey(key))
  {
    if (jsonObject.TryGetValue(key, out value))
    {
      if (value.ValueType == JsonValueType.String)
      {
        if (double.TryParse(jsonObject.GetNamedString(key), 
          out parsedValue))
        {
          returnValue = parsedValue;
        }
      }
      else if (value.ValueType == JsonValueType.Number)
      {
        returnValue = jsonObject.GetNamedNumber(key);
      }
    }
  }
  return returnValue;
}

Dans la mesure où la méthode intégrée permettant d'extraire des nombres dans la classe JsonObject retourne un double, et où les valeurs de données sont souvent exprimées sous forme d'entiers, le code suivant montre comment la méthode GetIntegerValue encapsule la méthode GetDoubleValue et convertit le résultat en entier :

public static int? GetIntegerValue(this JsonObject jsonObject, 
  string key)
{
  double? value = jsonObject.GetDoubleValue(key);
  int? returnValue = null;
  if (value.HasValue)
  {
    returnValue = Convert.ToInt32(value.Value);
  }
  return returnValue;
}

Ajout de la prise en charge de fabrique

Maintenant que la classe JsonObject a été étendue de façon à inclure une prise en charge de niveau plus élevée pour l'extraction de données afin de les convertir en types primitifs, l'étape suivante consiste à utiliser cette prise en charge dans les classes de fabrique chargées de prendre des chaînes JSON entrantes et de retourner une instance d'un objet de domaine alimenté.

Le code suivant décrit comment une personne est modélisée dans le système :

internal class Person
{
  public int Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public bool? IsOnWestCoast { get; set; }
}

Le code suivant montre la méthode Create de la classe PersonFactory qui accepte une chaîne :

 

public static Person Create(string jsonString)
{
  JsonValue json;
  Person person = new Person();
  if (JsonValue.TryParse(jsonString, out json))
  {
    person = PersonFactory.Create(json);
  }
  return person;
}

La figure 4 montre une méthode Create qui accepte une JsonValue. Ces méthodes Create utilisées conjointement sont chargées de prendre une chaîne brute et de retourner une instance d'une classe Person avec les données attendues dans chaque membre. Ces méthodes sont séparées et surchargées afin de fournir la prise en charge des tableaux JSON, comme nous le verrons à la section suivante.

Figure 4 Méthode Create de PersonFactory qui accepte une JsonValue

public static Person Create(JsonValue personValue)
{
  Person person = new Person();
  JsonObject jsonObject = personValue.GetObject();
  int? id = jsonObject.GetIntegerValue("id");
  if (id.HasValue)
  {
    person.Id = id.Value;
  }
  person.FirstName = jsonObject.GetStringValue("firstName");
  person.LastName = jsonObject.GetStringValue("lastName");
  bool? isOnWestCoast = jsonObject.GetBooleanValue("isOnWestCoast");
  if (isOnWestCoast.HasValue)
  {
    person.IsOnWestCoast = isOnWestCoast.Value;
  }
  return person;
}

Ajout de la prise en charge des tableaux

Il arrive que les données se présentent sous forme de tableaux d'objets plutôt que de simples objets. Le cas échéant, vous devez tenter d'analyser la chaîne comme un tableau en utilisant la classe JsonArray. La figure 5 montre comment la chaîne entrante est analysée dans un tableau, puis chaque élément est passé à la méthode Create pour l'analyse finale dans le modèle. Vous remarquerez qu'une nouvelle instance de la liste Person est tout d'abord créée afin que, si la chaîne n'est pas analysée dans un tableau d'objets, le résultat soit un tableau vide. Vous éviterez ainsi les exceptions inattendues.

Figure 5 Méthode CreateList de PersonFactory

public static IList<Person> CreateList(string peopleJson)
{
  List<Person> people = new List<Person>();
  JsonArray array = new JsonArray();
  if (JsonArray.TryParse(peopleJson, out array))
  {
    if (array.Count > 0)
    {
      foreach (JsonValue value in array)
      {
        people.Add(PersonFactory.Create(value));
      }
    }
  }
  return people;
}

Ajout de classes de prise en charge

L'étape suivante consiste à créer un objet chargé d'utiliser la classe de fabrique et de faire quelque chose d'intéressant avec les instances de modèle obtenues. La figure 6 montre comment les chaînes individuelles et les chaînes de tableau JSON sont utilisées, puis manipulées comme objets fortement typés.

Figure 6 Implémentation de ContactsManager (sans prise en charge asynchrone)

using System.Collections.Generic;
public sealed class ContactsManager
{
  private string AddContact(string personJson)
  {
    Person person = PersonFactory.Create(personJson);
    return string.Format("{0} {1} is added to the system.",
      person.FirstName,
      person.LastName);
  }
  private string AddContacts(string personJson)
  {
    IList<Person> people = PersonFactory.CreateList(personJson);
    return string.Format("{0} {1} and {2} {3} are added to the system.",
      people[0].FirstName,
      people[0].LastName,
      people[1].FirstName,
      people[1].LastName);
  }
}

Prise en charge de l'interaction asynchrone

Les appels de ce type dans les méthodes de composants WinRT doivent être effectués de façon asynchrone dans la mesure où les messages JSON ont la capacité de croître jusqu'à une taille arbitraire, ce qui risque d'introduire de la latence dans votre application.

Le code suivant comprend la méthode ajoutée à ContactsManager pour prendre en charge l'accès asynchrone à la méthode AddContact :

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.Foundation;
public IAsyncOperation<string> AddContactAsync(string personJson)
{
  return Task.Run<string>(() =>
  {
    return this.AddContact(personJson);
  }).AsAsyncOperation();
}

La méthode AddContactAsync accepte la chaîne JSON, puis commence une Task qui est chargée d'exécuter la méthode AddContact. Une fois cette Task terminée, une réponse est envoyée à la promesse JavaScript, une opération facilitée par la prise en charge de l'interface IAsyncOperation. Vous trouverez la source complète de la classe ContactsManager avec la prise en charge asynchrone des méthodes AddContact et AddContacts dans le code à télécharger qui accompagne cet article.

Promesses tenues en JavaScript

La dernière pièce du puzzle consiste à utiliser la classe ContactsManager en JavaScript et à appeler la classe utilisant le modèle de promesse. L'approche utilisée dans cet exemple implémente un modèle de vue qui passe les données modélisées au composant WinRT, puis attend une réponse. Les données utilisées pour la transmission au composant sont définies à la figure 7 qui inclut un seul objet JSON ainsi qu'un tableau.

Figure 7 Source de données JSON

var _model = {
  contact: {
    id: 1000,
    firstName: "Craig",
    lastName: "Shoemaker"
  },
  contacts: [
    {
      id: 1001,
      firstName: "Craig",
      lastName: "Shoemaker",
      isOnWestCoast: "true"
    },
    {
      id: 1002,
      firstName: "Jason",
      lastName: "Beres",
      isOnWestCoast: "0"
    }
  ]
}

Le modèle de vue, tel que représenté à la figure 8, comprend un membre pour le modèle et des membres pour les messages qui sont retournés depuis le composant WinRT. L'infrastructure de liaison de la bibliothèque Windows pour JavaScript (WinJS) permet de lier les messages retournés par la réponse aux éléments HTML. Une liste complète du module de page est disponible dans le code à télécharger qui accompagne cet article afin que vous puissiez voir comment chaque élément a sa place.

Figure 8 Modèle de vue qui utilise ContactsManager

var _vm = {
  ViewModel: WinJS.Binding.as({
    model: _model,
    contactMsg: "",
    contactsMsg: "",
    addContact: function () {
      var mgr = ParseJSON.Utility.ContactsManager();
      var jsonString = JSON.stringify(_vm.ViewModel.model.contact);
      mgr.addContactAsync(jsonString).done(function (response) {
        _vm.ViewModel.contactMsg = response;
      });
    },
    addContacts: function () {
      var mgr = ParseJSON.Utility.ContactsManager();
      var jsonString = JSON.stringify(_vm.ViewModel.model.contacts);
        mgr.addContactsAsync(jsonString).done(function (response) {
          _vm.ViewModel.contactsMsg = response;
        });
      }
  })
};

Vous remarquerez que si vous souhaitez lier les fonctions addContact ou addContacts à un bouton lors de la liaison de données, vous devez exécuter la fonction WinJS.Utilities.requireSupportedForProcessing, en passant une référence à la fonction de votre modèle de vue.

La dernière étape consiste à ajouter les éléments et attributs appropriés au HTML afin de prendre en charge la liaison. Un élément div joue le rôle de conteneur de liaison principal pour les éléments de liaison et il est marqué en définissant data-win-bindsource="Application.Pages.Home.ViewModel". Les éléments d'en-tête sont ensuite liés à leurs membres de données en fournissant les valeurs appropriées aux attributs data-win-bind :

    <section aria-label="Main content" role="main">
      <div data-win-bindsource=
        "Application.Pages.Home.ViewModel">
        <h2 data-win-bind="innerText: contactMsg"></h2>
        <hr />
        <h2 data-win-bind="innerText: contactsMsg"></h2>
      </div>
    </section>

Et voilà ! La création d'applications du Windows Store avec JavaScript vous permet d'utiliser vos compétences existantes issues du Web afin de créer des applications d'interface utilisateur modernes et natives. Il existe toutefois un certain nombre de critères qui différencient les deux plateformes. Un niveau de prise en charge faible est disponible pour l'analyse des données JSON par l'intermédiaire de l'espace de noms Windows.Data.Json, mais vous pouvez ajouter une prise en charge plus importante grâce à quelques extensions apportées aux objets existants.

Craig Shoemaker développe des logiciels, réalise des podcasts, tient un blog et se charge également d'aspects plus techniques. Il est aussi auteur pour Code Magazine, MSDN et Pluralsight. Pendant son temps libre, il aime rechercher une botte de foin dans laquelle cacher sa précieuse collection d'aiguilles. Vous pouvez le suivre sur Twitter à l'adresse twitter.com/craigshoemaker.

Merci aux experts techniques suivants d'avoir relu cet article : Christopher Bennage (Microsoft), Kraig Brockschmidt (Microsoft) et Richard Fricks (Microsoft)
Christopher Bennage est développeur chez Microsoft auprès de l'équipe Modèles et pratiques. Son travail consiste à découvrir, collecter et encourager les pratiques qui réjouissent les développeurs. JavaScript et le développement de jeux (informels) figurent parmi ses centres d'intérêt actuels. Il tient un blog dont l'adresse est dev.bennage.com.

Kraig Brockschmidt travaille chez Microsoft depuis 1988. Il apporte son aide aux développeurs par l'intermédiaire de ses écrits, de ses enseignements , de ses conférences et d'un engagement direct. Il occupe le poste de responsable de programme senior au sein de l'équipe Windows Ecosystem. Il collabore avec des partenaires clés sur la création d'applications du Windows Store et apporte ses connaissances dans le domaine au reste de la communauté des développeurs. Son ouvrage le plus récent s'intitule « Programming Windows 8 Apps in HTML, CSS, and JavaScript » (un livre électronique gratuit publié chez Microsoft Press). En outre, vous trouverez son blog à l'adresse kraigbrockschmidt.com/blog.

Richard Fricks travaille en collaboration avec la communauté des développeurs depuis 20 ans et il a plus récemment participé à la conception de la stratégie de l'API Windows Runtime pour les médias. Il a également aidé la communauté des développeurs à adopter de nouvelles fonctionnalités Windows 8. Il est actuellement responsable de programme au sein de l'équipe chargée de l'adoption des scénarios Windows.