JSON optimisation récupération des données
Rocou
Membre
Bonjour,
Je récupère un JSON sont voici la forme:
{"status":{"elapsed":237,"timestamp":"2022-05-16T08:37:07.217103212Z"},"data":{"market_id":"0f5e624a-4730-403e-b141-0a905064e0e4","values":[[1651770000000,35303.39,35348.09,35016.56,35084.99,58.93334],[1651773600000,35079.15,35105.57,34340.58,34657.65,88.51477999999999],...
J'ai tout mis dans un dictionnaire, tout fonctionne correctement. Cependant, je voudrais extraire certaines données associées à la clé "values".
Existe-t-il un outil pour faire cela ou faut-il travailler caractère par caractère?
Je dois récupérer toutes les avant-dernières valeurs. Dans mon exemple il s'agit de 35084.99
et de 34657.65
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Tu ne peux pas utiliser NSJSONSerialization ?
Je l'utilise déjà mais peut-être n'en ai-je pas compris toutes les possibilités?
Au temps pour moi...
Sous quelle forme (quels objets) apparait values dans ton dictionnaire ? values devrait être un NSArray de NSArray de NSNumber, non ?
C'est taggué Swift ? Swift 4+?
Si c'est le cas, utilise
JSONDecoder
&Codable
, sinon,(NS)JSONSerialization
.values
est un Array d'array de Double, un[[Double]]
.Donc, itères sur
values
, et récupère la valeur à l'index 4.Sur ta structure, tu peux avoir une lazy var ou une computed property qui peut te retourner un array de Double que tu souhaites.
Du coup, quelque chose dans ce genre :
Il manque un niveau de parsing (
dictionary[@"data"][@"values"]
), mais dans l'idée ça doit être ça en Objective-C.Oui je viens de m'en rendre compte
En testant ce bout code sur ton extrait JSON :
j'obtiens bien les 2 valeurs que tu cherches Rocou...
Effectivement, il y avait un truc que je n'avais pas compris.
Merci beaucoup (sinon j'ai abandonné ObjectiveC, je ne travaille plus qu'en swift)
Alors vu que tu utilises Swift, tu n'as pas spécifié si tu utilisais Swift 4+, mais quand j'ai lu "dictionnaire", j'avoue ne pas avoir été fan de cette réponse avec
Codable
.Si jamais
Codable
t'es autorisé, voici un p'tit sample dans Playground :L'avantage de
lazy
, c'est que cela ne sera calculé qu'une seule fois, mais cela t'obliges à mettre desvar
un peu partout et pas deslet
... Donc, en fonction de ton utilisation : si tu ne le récupères pas à tout bout d'champ, que ton application est assez légère, car il faut avouer que faire ce simplemap()
n'est pas excessif non plus sur le CPU, en tout cas sur l'exemple isolé cité...J'ai encore besoin d'un petit coup de pouce. Je n'arrive pas à construire mes déclarations avec un json plus complexe. Sinon, c'est exactement le même but, récupérer les 4e chiffres de "values" (
34070.01
,34167.18
et28740.01
)Cela ne devrait rien changer au code que j'ai proposé, mon code devrait marcher, juste que les champs rajoutés sont ignorés. La structure globale du JSON étant la même...
Il faut juste que
values.map { $0[4] }
à la place de4
, il y ait le bon index, ici3
.C'est parce que j'obtiens cette erreur:
La différence avance ton code est que je n'intègre évidemment pas le JSON dans mon code, je récupère le contenu d'un fichier json.
Voici le bout de code:
Ensuite, arrive ton code et je remplace
json.utf8
parstringmonDico.uft8
Alors ok, c'est normal.
Tu utilises mon code directement, j'appelle
JSONDecoder
à la place deJSONSerialization
.Pourquoi cela rate ?
Car tu as désérialisé déjà avec
JSONSerialization
, et tu as[String: Any]
. Ensuite, tu le transformes enString
viamonDict.description
, sauf que ledescription
pour un Dictionary, c'est custom made in Apple, cela n'est en aucun cas du JSON ! Donc ensuite, il te dit en erreur : bah non, ce string n'est pas un JSON valide.Oublie totalement
stringmonDico
.Ensuite, je vois une deuxième horreur.
dataTask(with:)
, cela fait déjà la requête (enfin, surtout si tu as un.resume()
en fin de cette dernière pour la lancer), or tu fais après avoir terminerData(contentsOf:)
, donc tu refais la requête encore !Enfin, j'ai peur du fait que tu gardes
stringmonDico
en variable comme ça. Y'a pas assez de contexte, mais je me demande comment tu gères l'asynchrone ici...Cela devrait être quelquechose de ce goût là (écrit à la main, sans compiler, donc y'a peut-être une faute de typo)
À appeler ainsi :
Je ne comprends pas la déclaration de la fonction:
((Response?) -> Void)?)
S'il n'y a rien dans Response alors on renvoie Void.
Est-cela? et à quoi sert le second ?
Par ailleurs, à la compilation, Xcode me dit que Response n'est pas dans le scope, je ne comprends pas pourquoi.
C'est une closure. L'équivalent Objective-C c'est Block.
C'est exactement ce que tu as appelé avec
dataTask(with:completionHandler:)
dont la déclaration est:Si responseDecoded a raté au decoding, tu veux l'inventer ?
, avec d'un côté
Tu peux également renvoyer une
Error
, comme le fait ``dataTask(with:completionHandler:)data
et de l'autreerror
(enfin presque). Il te suffit de modifier la déclaration de la closure du coup.Voyant cela, cela semble confirmer que tu ne maîtrises pas l'asynchrone...
Certains te diront mais tu t'en fiches, y'a async/await maintenant en Swift je sais plus quelle version récente, mais bon, y'a pas mal de doc/tutos avec des closures, alors les comprendre est un plus.
Cela dépend d'où tu as déclaré
struct Response: Codable
. Je l'avais mis dans la méthodevaluesInJSON
car c'est dans mon Playgrounds, et j'ai peut-être une autre structureResponse
quelque part (un vrai bazar). Donc pour éviter des doublons...Je ne maitrise pas du tout l'asynchrone, c'est le moins que l'on puisse dire.
Cependant j'ai corrigé ce qu'il fallait et maintenant tout fonctionne impeccablement, je te remercie beaucoup.
A un détail près:
completion(decodedResponse)
. Cela ne passe pas à la compilation, j'ai droit à ce message:J'ai regardé sur internet la notion de
completion
, pour le moment je n'ai pas compris grand chose. Dans le contexte ci-avant, quel est l'objectif decompletion(decodedResponse)
?completion?(decodedResponse)
, c'est un optional pardon. Je l'ai mis sur les autres, mais pas sur ce dernier.Je suppose que mon nouveau problème vient du fonctionnement asynchrone: en effet, je voudrais pouvoir manipuler ailleurs dans mon programme le tableau obtenu:
response.data.computedForthValues
Le problème est que la fonction
makeTheRequest()
se termine avant que la requête aboutisse. Par conséquent, le tableau que je tente de retourner est vide.Y-a-t-il une sorte de "wait" qui permettrait d'attendre que la requête soit exécutée afin que je puisse récupérer les valeurs après avoir appelé la fonction
makeTheRequest
?Où alors il existe un autre fonctionnement qui m'échappe?
Oui, il te manque le concept de l'asynchrone.
Pour le mettre en avant, mettons en exergue l'asynchrone sur URLSession:
Si on regarde les prints dans le console, tu devrais t'attendre si tu ne maîtrise pas l'asynchrone à voir :
Or en réalité, c'est :
La closure, il faut vioir ça comme un callback (si le terme te parle), une sorte de mini-méthode qui sera appelé en temps et en heure. Ici, quand tu as la réponse de ta requête.
Tu as donc mis du code à l'intérieur de la closure pour réagir à la
data
que tu as reçu.Donc, tu es capable de faire la même chose sur ta méthode
makeTheRequest(completion:)
, non ? Tu vois l'analogie ?Je ne sais pas ce que tu fais avec tes valeurs, mais :
Considère que tu ne les as pas (au lancement, l'appel réseau n'a pas été effectué).
Puis :
Attention, closure ne veut pas dire qu'elle sera appelée de manière asynchrone.
Un contre exemple simple est
filter()
:Console:
Je te remercie, je vais étudier tout cela.
Cependant, est-il possible de ne pas utiliser de closure? Car en l'occurence j'ai besoin d'un fonctionnement totalement synchrone:
Je comprends parfaitement l'intérêt d'un fonctionnement asynchrone mais pour le coup, je n'en ai pas besoin.
Pourquoi ne continues-tu pas ton traitement dans la closure de
makeTheRequest(completion:)
?Vu que tu fais une requête sans header, et en GET, tu peux utiliser
Data(contentsOf:)
, et c'est synchrone. Cela bloquera cependant le thread en cours, et si c'est le main thread, ton UI sera bloquée...Tu peux utiliser les async/await en Swift récent, cela rend le code plus linéaire, mais cela ne fait que "cacher" les closures...
Tu veux dire:
Mais c'est bien le problème, justement. A ce niveau, je n'ai pas encore récupéré les données.
Ah si, à ce moment là, tu as
response
! Ce ne sont pas les données que tu attendais ?En détail :
Oui, j'ai response mais c'est le json complet. Ce que je veux c'est le tableau contenant les 4e nombres de la clé "values" du json.
Tu utilises donc
response.data.computedForthValues
, c'est ton tableau de Double ça.C'est la première chose que j'ai testée mais cela ne fonctionne pas:
Le print ne semble pas être exécuté. Il ne se passe rien du tout.
response
est nil alors?guard let response = response else { return }
est executé ? N'oublie pas que c'est asynchrone. Tu as un // Handle HTTP request error, ça print quelque chose dedans?Un
print
depuis un autre thread que le main n'a aucune chance d'afficher quoi que ce soit.Pas sur iOS en tout cas, et il ne me semble pas avoir vu ça sur macOS non plus...