Un’azione di connettore ti restituisce trecento righe di JSON annidato e a te ne serve un campo. Puoi scriverti un ciclo for con dentro un if, e funziona, ma diventa illeggibile al terzo livello. FortiSOAR ti dà due linguaggi di interrogazione richiamabili come normali filtri Jinja: json_query e yaql. L’esame chiede di leggerli entrambi e di confrontarli, e la differenza fra i due non è stilistica.
json_query: JMESPath applicato all’output di uno step
json_query arriva dal mondo Ansible ed è costruito su jmespath: la sintassi accettata è esattamente quella di JMESPath, senza dialetti FortiSOAR. Serve quando hai una struttura JSON complessa e ti serve estrarne un sottoinsieme. Il caso della documentazione: uno step restituisce una lista di oggetti con name e state, e tu vuoi solo i nomi di quelli in stato running.
[
{"name": "a", "state": "running"},
{"name": "b", "state": "stopped"},
{"name": "b", "state": "running"}
]
{{ vars.steps.<step_name>.keyname | json_query("[?state=='running'].name") }}
Il risultato è la lista ["a", "b"]. Leggila bene, quella espressione, perché ci sono tre cose che si sbagliano di continuo. Le virgolette sono annidate: doppie fuori, per delimitare l’argomento del filtro, e singole dentro, per il letterale JMESPath. Se inverti l’ordine rompi il template. Poi: il confronto con un letterale stringa vuole le virgolette singole, mentre un letterale JSON, per esempio un numero o un booleano, in JMESPath si scrive fra backtick. E infine il risultato di una proiezione è sempre una lista, anche quando i match sono zero o uno: se il campo di destinazione vuole un valore singolo devi indicizzare tu.
Ultimo prerequisito, spesso invisibile: json_query lavora su una struttura già deserializzata. Se lo step ti ha restituito una stringa JSON e non un oggetto, prima devi passarla per from_json o toDict, altrimenti la query non trova nulla e non ti dice perché.
yaql: l’altro linguaggio, che sa anche ricostruire
Il filtro yaql è stato aggiunto nella 7.2.0 come linguaggio di filtro alternativo a Jinja. YAQL è scritto in python, ha una libreria standard di funzioni di interrogazione ed è estendibile con funzioni proprie. La regola di lettura è una sola: il simbolo del dollaro indica «questo» oggetto, cioè l’elemento corrente. E lo stesso simbolo può significare cose diverse dentro la stessa query: in $.pets.flatten().where($.type='cat') il primo si riferisce a ogni oggetto User, il secondo a ogni oggetto Pets.
{{ {"var1":1,"var2":"a"} | yaql('$.var1') }}
{{ "test" | yaql('$.toUpper()') }}
{{ vars.users | yaql("$.where($.firstname.startsWith('B'))") }}
{{ vars.users | yaql("$.where('@example.com' in $.email)") }}
{{ vars.users | yaql("$.where($.pets.where($.type='cat').any())") }}
Nota due capacità che JMESPath non ha. La prima: YAQL applica metodi anche a uno scalare, e infatti la stringa test diventa TEST senza che ci sia una collezione di mezzo. La seconda, molto più utile in un SOC, è la ricostruzione di una struttura. L’esempio ufficiale ripulisce la risposta di un lookup di reputazione togliendo tutti i campi vuoti.
{{ data | yaql('dict($.items().where(bool($[1])))') }}
Da un oggetto con av_cate, wf_cate vuota, ioc_tags vuota, confidence e reference_url, restano solo i tre campi valorizzati. Provare a ottenere lo stesso con json_query significa elencare a mano le chiavi che ti interessano: JMESPath seleziona e proietta, non ricompone per condizione sul valore.
Quale dei due, e quando nessuno dei due
Regola pratica da SOC. Se devi filtrare una lista di oggetti su un campo e tirar fuori una o due proprietà, json_query è più corto e più leggibile. Se devi navigare relazioni annidate, applicare metodi di stringa, testare l’esistenza con any() o rimontare un dizionario, prendi yaql.
Poi c’è la terza superficie di interrogazione, quella che a volte è la risposta giusta a una domanda formulata come se riguardasse i filtri: la Query API. Non è un filtro Jinja, gira lato server sui record di un modulo e usa un oggetto JSON completamente diverso, con logic, filters, field, operator e value.
POST /api/query/incidents
{
"logic": "AND",
"filters": [
{ "field": "status.itemValue", "operator": "eq", "value": "Open" }
]
}
Gli operatori sono eq, neq, lt, lte, gt, gte, in, nin, contains, like, notlike, isnull. Non esistono dentro json_query, così come [?state=='running'] non ha alcun significato dentro un filtro della Query API. Se i dati sono ancora nel database e sono tanti, filtra con la Query API e porta in memoria solo ciò che serve. Se i dati sono già l’output di uno step, filtrali con json_query o yaql.
Gli errori che li fanno fallire
Sia json_query sia yaql restano filtri Jinja, quindi l’auto-conversion vale anche per loro: se il campo di destinazione vuole una stringa e la query restituisce una lista, racchiudi l’intera espressione fra virgolette. I nomi dei filtri e delle funzioni sono case-sensitive. E siccome nessuno dei due segnala «nessun match» in modo diverso da «lista vuota», l’unico modo serio di lavorare è il Jinja Editor: incolli il JSON reale nell’area di input, clicchi Render e guardi cosa esce prima di salvare lo step.