Transformation JavaScript
Source: tutorial/08_javascript_transform See in Playground
Lit un objet JSON par ligne, calcule une note en JavaScript à partir de son champ score, et réécrit les enregistrements notés. Les données d’exemple, students.jsonl, utilisent des noms avec des caractères UTF-8 complets (Amélie, Øystein, Carmiña, Łukasz), lus et réécrits sans aucun traitement particulier.
Exécution
cd tutorial/08_javascript_transform
melodium run Compo.toml --input_file students.jsonl{"grade":"A","name":"Amélie","score":92}
{"grade":"E","name":"Øystein","score":58}
{"grade":"C","name":"Carmiña","score":74}
{"grade":"FX","name":"Łukasz","score":45}Optionnel : ajoutez --api-report et un jeton d’API (MELODIUM_API_TOKEN) pour voir la trace complète de cette exécution sur Cadence.CI .
Fonctionnement
Le modèle Grader charge une fonction JavaScript grade() une seule fois au démarrage, et cette même fonction compilée est réutilisée pour chaque ligne traitée :
model Grader() : JavaScriptEngine {
code = ${{function grade(input) {
var g = 'F';
if (input.score >= 90) g = 'A';
else if (input.score >= 80) g = 'B';
else if (input.score >= 70) g = 'C';
else if (input.score >= 60) g = 'D';
else if (input.score >= 50) g = 'E';
else if (input.score >= 45) g = 'FX';
return { name: input.name, score: input.score, grade: g };
}
}}
}La chaîne en bloc brut ${{...}} est la façon standard d’embarquer du code JavaScript multiligne sans avoir à échapper les sauts de ligne ou les guillemets.
Les lignes sont extraites de la même façon que dans les exemples précédents de traitement de texte (split, flatten, trim, lignes vides écartées), puis chaque ligne est analysée en une valeur Json et transmise à process, qui appelle la fonction JS grade(value) définie dans le code du modèle :
treatment main(
const input_file: string = "students.jsonl",
const output: string = "grades.txt"
)
model engine: Grader()
{
startup()
read: readTextLocal(path=input_file)
startup.trigger -> read.trigger
splitLines: split(delimiter="\n", inclusive=false)
lines: flatten<string>()
trimmed: trim()
read.text -> splitLines.text,splitted -> lines.vector,value -> trimmed.text
isBlank: exact(pattern="")
notBlank: not<bool>()
nonBlank: filter<string>()
trimmed.trimmed -> isBlank.text
isBlank.matches -> notBlank.value
trimmed.trimmed -> nonBlank.value
notBlank.not -> nonBlank.select
parsed: toJson()
body: unwrapOr<Json>(default=|null())
graded: process[engine=engine](code="grade(value)")
result: unwrapOr<Json>(default=|null())
asText: toString<Json>()
nonBlank.accepted -> parsed.text,json -> body.option,value -> graded.value,result -> result.option,value -> asText.value,into -> logGrades.messages
logGrades: logInfos(label="grade")
withNewline: entry(key="line")
asLine: format(format="{line}\n")
write: writeTextLocal(path=output)
logDone: logInfoMessage(label="js", message="grades written")
asText.into -> withNewline.value,map -> asLine.entries,formatted -> write.text
write.finished -> logDone.trigger
}À l’intérieur de la fonction JavaScript, value est l’objet analysé (input.score, input.name), exactement l’accès champ par champ que les traitements json seuls ne permettent pas, comme vu dans les deux étapes précédentes du tutoriel. process renvoie un Option<Json> (none si le code lève une exception ou renvoie quelque chose qui ne peut pas être converti en JSON), et unwrapOr fournit alors une valeur de repli pour que le pipeline ne se bloque jamais sur un enregistrement invalide.
Deux points de dépaquetage distincts méritent d’être notés :
unwrapOr<Json>(default=|null())avantprocess: un JSON d’entrée malformé devient silencieusementnullplutôt que d’interrompre le pipeline.unwrapOr<Json>(default=|null())aprèsprocess: une erreur d’exécution JS produit elle aussi unnullgracieux plutôt que d’arrêter l’exécution.
Chaque résultat est journalisé et écrit dans grades.txt, un objet JSON par ligne, en reprenant le même idiome “entry + format + saut de ligne” utilisé pour construire des rapports texte tout au long du tutoriel. Comme le code du modèle est compilé une seule fois au démarrage, et que le paramètre code de process ("grade(value)") n’est que l’expression évaluée pour chaque élément, la logique de transformation n’est écrite qu’une fois et réutilisée pour chaque ligne sans recompilation. Cela fait de javascript::process la solution de repli pour l’accès structuré au JSON : dès qu’une tâche nécessite de lire ou de construire des champs précis d’un objet ou d’un tableau JSON, c’est l’outil à privilégier plutôt que d’essayer de l’assembler uniquement à partir des primitives du paquet json.
Dépendances
[dependencies]
std = "0.10.4" # flux de base, journalisation, structures de données
fs = "0.10.4" # lecture/écriture de fichiers locaux
json = "0.10.4" # parsing et sérialisation JSON
javascript = "0.10.4" # moteur JavaScript embarqué