Json filter problème de brackets

Bonjour,

J'ai une question en lien avec le parsing d'un JSON à l'aide du filtre JSON.

J'ai appliqué le filter JSON pour parser un bout de message de type JSON. Le JSON en question est de ce format :

{
  " ResponseHeader": {
    "Response": "Accepted"
  },
  "ResponseBody": {
    "messageInfo": {
      "accessChannel": "WEB",
      "language": "fr"
    },
    "response": {
      "code": "Accepted"
    },
    "services": [
      {
        "id": 1,
        "code": "Balance"
      },
      {
        "id": 2,
        "code": "****"
      },
      {
        "id": 30,
        "code": "****"
      },
     
    ]
  }
}

Le parsing s'exécute sans aucune erreur. par contre, je n’arrive pas à accéder à des champs à l'intérieur des brackets [ ], par exemple le champservices.id. J'ai tenté un formatage manuellement de mon JSON, sur un petit exemple, pour enlever les [ ] et ça marche. La solution manuelle est très lourde et trop risquée, c'est même une solution pas propre du tout, car j'ai des données non structurées et très compliquées à appréhender.

Je pense qu'il doit exister une solution triviale à ce problème, étant donné que les [ ] font parties du format JSON. Mais en cherchant sur internet, je ne trouve rien à ce sujet.

Est-ce que vous avez une piste à me conseiller ?

Je vous remercie par avance.

En sortie (stdout, élasticsearch, ...), est ce que le tableau json est bien la et bien structuré comme attendu?
Peux tu donner la configuration des filtres derrière que tu utilises pour travailler sur le tableau json ?

Voilà ce que j'ai sur kibana :

en format JSON :

Remarque : quand je cherche à gauche le champ en question pour afficher sa valeur, pour chaque ligne, il ne le trouve pas ( car il me propose que les champs non vides), d'ailleurs quand je force pour qu'il m'affiche même les champs non vides, le champ en question est mappé, mais sa valeur est non affichée, comme tu peux le voir sr cette capture.

Pour accéder aux champs , j'utilise la syntaxe suivante :

 "[RequestHeader][ResponseBody][contracts][codeContracts]"

La sortie de stdout { codec => json } est ainsi ( je ne peux pas prendre tout en captur car le json est énorme)

Pour être certain que le format Json de ce bloc

 "[RequestHeader][ResponseBody][contracts][codeContracts]"

est valide, je l'ai copié et j'étais le valider en solo :

voilà aussi la syntaxe que j'utilise sur logstash pour parser le JSON (rien de plus classique)

json
						{
							source => "RequestHeader"
							target => "RequestHeader"
						}

Ton tableau json est bien parsé comme il faut.

Pour les filtres, je voudrais connaître les filtres après “json” qui tentent d’exploiter ce tableau.
Pour te dire quoi changer.

le filtre que j'ai tenté d'utilsier et le mutate avec un update à l'intérieur pour juste anonymiser les champs.
par exemple:

	mutate 
	{
		convert => {"[RequestHeader][ResponseBody][accounts][balance]"=> "string"}
                update  => {"[RequestHeader][ResponseBody][accounts][balance]"=>"************"}
         }

le comble c'est que si on exploite ce mapping dans un dashboard, il le trouve et affiche ses valeurs. mais quand on veut l'exploiter sur discover ou logstash ça ne marche pas.

Je me demande si il faudrait une syntaxe plus adaptée pour ce problème en particulier ?

OK.
Bon, globalement, saches que la suite Elastic n'est pas très copain avec les tableaux d'objets.

Pour ta conf Logstash, si ton but est d'anonymiser le champ "balance" de ton tableau d'objets, voici comment tu peux faire :

ruby {
        code => "
            accounts = event.get('[RequestHeader][ResponseBody][accounts]')
            accounts.each do |account|
                account['balance'] = '*****'
            end
            event.set('[RequestHeader][ResponseBody][accounts]', accounts)
        "
    }

Ensuite, pour que ton tableau d'objets soit correctement interprété, il faut définir le mapping suivant dans Elasticsearch :

{
  "type": "nested"
}

Enfin, il faut utiliser minimum Kibana 7.6 pour avoir les tableaux d'objet "nested" supportés, au moins dans la barre de recherche avec la syntaxe KQL.

j'ai par contre plus que 500 champs à anonymiser.
Il existe à un moyen d'appliquer dans Elastic

{
  "type": "nested"
}

? Merci
en plus j'ai des champs avec des mapping très longs comme ce cas :

[RequestHeader][ResponseBody][cardShipment][acknowledgedByPerson][channel][institution][currency][name]

et dans chaque branche il y a un problème de mapping. Est ce qu'il faut du coup les faire en cascade, c'est à dire, dans l'exemple que je t'ai donné, traité [cardShipment] en premier puis traiter ses fils ainsi de suite jusqu'à ce qu'on arrive à la feuille ?

car en réalité le parsing de json commence après la RepsonseBody.

Encore merci pour ton aide.

Hey,

Tu peux normalement accèder a la veleur via :

[ResponseBody][messageInfo][services][0]

En réalité chaque élément de ton tableau json a un "id" implicite de part sa position dans le tableau :

image

Il est possible d’itérer à travers cette valeur ( [0] ) pour accéder à l'ensemble des valeurs du tableau.

Tu peux être plus précis à ce sujet ?

Pour appliquer le mapping "nested" dans Elasticsearch, il faut le postitionner dans le mapping de l'index template poussé par Logstash, dans la sortie "elasticsearch".
Tu as un champ "template", qui te permet de personnaliser le mapping Elasticsearch de l'index créé.
Tu peux notamment définir dans un index template, des dynamic templates, permettant de customiser le mapping d'un champ basé sur son nom ou son type.

Côté Logstash, je t'ai montré une boucle d'exemple sur "accounts" qui anonymise le champ "balance", mais tu peux faire autant de boucles que nécessaire.
Après, pour tous les champs "à plat" (qui ne sont pas dans un tableau d'objets), tu peux simplement utiliser le filtre "mutate".

De manière générale, je ne te conseille pas vraiment d'avoir beaucoup de tableaux d'objets dans Elasticsearch et encore moins des tableaux dans des tableaux.
Si tu as bcp de tableaux d'objets, tu peux éventuellement utiliser le filtre "split" pour spliter ton document en autant de documents que d'entrées de tableau.

2 Likes

Pourquoi cette écriture

ruby {
        code => "
            accounts = event.get('[RequestHeader][ResponseBody][accounts]')
            accounts.each do |account|
                account['balance'] = '*****'
            end
            event.set('[RequestHeader][ResponseBody][accounts]', accounts)
        "
    }

au lieu d'une plus simple comme cela :

ruby
{
code => 
"
event.set('[RequestHeader][ResponseBody][accounts][balance]', '************' )
"
}

Car j'ai essayé cette dernière écriture sur tous mes champs à anonymiser et ça marche sauf que j'ai un ralentissement important. Pour info, pour ce flux seulement, j'ai 220 champs à anonymiser.

Ps: je n'ai pas encore testé d'ajouter le mapping " nested" à ce que j'ai fait.

Salut,

je veux accéder à certains champs du tableau mais je ne connais pas au préalable leurs "id".

Le script ruby est là pour parcourir les entrées du tableau d'objets, et mettre à jour une (ou plusieurs) propriété(s) de chaque entrée du tableau.
Si tu n'avais pas de tableau d'objets, un simple "mutate" aurait suffi.
Ainsi, "balance" n'est pas un champ de "accounts", mais un champ de chaque entrée du tableau "accounts".

Je t’avoue que je n’arrive pas à faire la différence là : « balance" n'est pas un champ de "accounts", mais un champ de chaque entrée du tableau "accounts". »
Ce que j’avais compris à la base c’est que le script Ruby récupère le sous arbre « le fils » de accounts, et « account » c’est le mapping jusqu’à « accounts », c’est à dire [requestheader]...[accounts]. Du coup, pour moi, opter pour cette écriture ou celle que j’ai proposée est la même chose. Et ce qui m’intriguait c’est le « nested », que je ne comprends pas jusqu’à maintenant.

Aussi quand j’ai testé avec Ruby sur plus de 200 champs, que j’ai anonymisé, la taille de mon index a augmenté de presque 20% et dans discover, dans kibana, les requêtes sont tres tres lentes...

Merci infiniment pour tes réponses.