dimanche 24 mars 2024

Transformer un contenu YAML en JAVA properties et vice versa / Transforming YAML content into JAVA properties and vice versa

Dans Mulesoft, deux formats sont privilégiés pour définir des propriétés: YAML et JAVA properties. L'amusant (enfin, pour ceux que le challenge tente...), c'est que selon l'outil, c'est l'un ou l'autre format qui est utilisé. Par exemple, sur Anypoint Studio, YAML est privilégié. Sur la console d'administration CloudHub, c'est Java Properties. Question: est-il possible de passer facilement de l'un à l'autre, par un petit script DataWeave ? Un exemple bien utile serait de pouvoir transformer la configuration en YAML d'une application en en ensemble de propriétés JAVA que l'on viendrait copier/coller dans les paramètres de l'onglet "settings" d'une application sur CloudHub.

On parle de passer de çà:

sftp:
host: "localhost"
port: "8081"

salesforce:
"token": "fghhgghjhgffddgghh"
"credentials":
"user": "Tintin"
"password": "VGFDZE"

à çà:

sftp.host = localhost
sftp.port = 8081
salesforce.token = fghhgghjhgffddgghh
salesforce.credentials.user = Tintin
salesforce.credentials.password = VGFDZE

Le script qui permet de le faire est le suivant:

%dw 2.0
output text/plain
fun yaml2json(o)=flatten (o pluck (v, k)-> (
v pluck (v2, k2)-> if ((typeOf(v2) as String)=='Object')
yaml2json((k ++ "." ++ k2):v2)
else
(k ++ "." ++ k2):v2
)
) reduce (l, a={})->(a ++ l)
fun json2Properties(o) = (o pluck {key:$$, value:$})
reduce (l, a="")-> a ++ (l["key"] ++ " = " ++ l["value"] ++ "\n")
---
json2Properties(yaml2json(payload))

Le principe consiste à prendre JSON comme format pivot, mais en réduisant la structure à un unique objets, dont les noms de chaque champ est son chemin dans la structure. Il s'agit donc d'obtenir la valeur suivante:

{
"sftp.host": "localhost",
"sftp.port": "8081",
"salesforce.token": "fghhgghjhgffddgghh",
"salesforce.credentials.user": "Tintin",
"salesforce.credentials.password": "VGFDZE"
}

Toute la magie est dans la fonction récursive yaml2json: la partie centrale (les deux pluck et le if ... else) agrège les noms des différents champs par le signe point, la partie périphérique (flatten et la reduction) aplatit la structure.

La fonction json2Properties construit une chaîne de caractères au format JAVA properties c'est à dire en concaténant toutes les lignes par un retour chariot et en associant clé et valeur à l'aide du signe égal. Pour que le résultat apparaisse proprement, il faut que le format de sortie du script soit défini comme: "plain/text".

Ce qu'on à fait dans un sens, peut-on le refaire dans l'autre ? La réponse est affirmative évidemment. Le script à utiliser est le suivant:

%dw 2.0
output application/yaml
fun field(o, v, i) = if (o[v.keys[i]] == null) (
o ++ if (i==sizeOf(v.keys)-1)
(v.keys[i]):v.value
else
(v.keys[i]):field({}, v, i+1)
) else (
o - v.keys[i] ++ (v.keys[i]):field(o[v.keys[i]], v, i+1)
)
fun prop2json(p) = p splitBy "\n"
map ($ splitBy "=")
map { keys: trim($[0]) splitBy ".", value: trim($[1]) }
reduce (v, a={}) -> field(a, v, 0)
---
prop2json(payload)

Cette fois une seule étape suffit car DataWeave reconnait le format YAML. Il suffit donc de construire la structure idoine (fonction prop2json) et de demander une sortie au format "application/yaml". La fonction procède en deux temps: elle construit une structure ou chaque association clé/valeur est structuré en autant d'objets associant un tableau des segments de clé et la valeur (c'est tout le début du contenu de la fonction) pour obtenir cela:

[
{
"keys": [
"sftp",
"host"
],
"value": "localhost"
},
{
"keys": [
"sftp",
"port"
],
"value": "8081"
},
{
"keys": [
"salesforce",
"token"
],
"value": "fghhgghjhgffddgghh"
},
{
"keys": [
"salesforce",
"credentials",
"user"
],
"value": "Tintin"
},
{
"keys": [
"salesforce",
"credentials",
"password"
],
"value": "VGFDZE"
}
]

Ce formatage est suivi par une réduction qui construit la structure arborescente en utilisant pour cela une fonction récursive "field". Le "if" extérieur de la fonction field" sépare le cas ou le segment rencontré n'est pas déjà présent dans la structure, du cas ou l'est. Le "if" intérieur sépare le cas ou la valeur rencontrée est de type primitif de celui ou c'est un objet (qui doit être être traité dans une nouvelle étape de la récursion). 
______________________________________________________________________

In Mulesoft, there are two preferred formats for defining properties: YAML and JAVA properties. The funny thing (well, for those who are up for a challenge...) is that, depending on the tool, one or the other format is used. On Anypoint Studio, for example, YAML is the preferred format. On the CloudHub administration console, Java Properties is used. Question: is it possible to switch easily from one to the other, using a small DataWeave script? A useful example would be to be able to transform an application's YAML configuration into a set of JAVA properties that you could be copy/paste'd into the settings tab of an application on CloudHub.

We're talking about starting with this:

sftp:
host: "localhost"
port: "8081"

salesforce:
"token": "fghhgghjhgffddgghh"
"credentials":
"user": "Tintin"
"password": "VGFDZE"

to obtain this:

sftp.host = localhost
sftp.port = 8081
salesforce.token = fghhgghjhgffddgghh
salesforce.credentials.user = Tintin
salesforce.credentials.password = VGFDZE

The script to do this is as follows:

%dw 2.0
output text/plain
fun yaml2json(o)=flatten (o pluck (v, k)-> (
v pluck (v2, k2)-> if ((typeOf(v2) as String)=='Object')
yaml2json((k ++ "." ++ k2):v2)
else
(k ++ "." ++ k2):v2
)
) reduce (l, a={})->(a ++ l)
fun json2Properties(o) = (o pluck {key:$$, value:$})
reduce (l, a="")-> a ++ (l["key"] ++ " = " ++ l["value"] ++ "\n")
---
json2Properties(yaml2json(payload))

The principle is to use JSON as the pivot format, but reduce the structure to a single object, with the name of each field being its path through the structure. The aim is to obtain the following value:

{
"sftp.host": "localhost",
"sftp.port": "8081",
"salesforce.token": "fghhgghjhgffddgghh",
"salesforce.credentials.user": "Tintin",
"salesforce.credentials.password": "VGFDZE"
}

The magic is in the recursive yaml2json function: the central part (the two plucks and the if ... else) aggregates the names of the various fields using the dot sign, while the peripheral part (flatten and reduction) flattens the structure.

The json2Properties function constructs a string in JAVA properties format, i.e. concatenating all lines with a carriage return and associating key and value with the equal sign. For the result to be displayed correctly, the script output format must be defined as "plain/text".

Can you do the same thing in the other direction? The answer is yes, of course. The script to use is as follows:

%dw 2.0
output application/yaml
fun field(o, v, i) = if (o[v.keys[i]] == null) (
o ++ if (i==sizeOf(v.keys)-1)
(v.keys[i]):v.value
else
(v.keys[i]):field({}, v, i+1)
) else (
o - v.keys[i] ++ (v.keys[i]):field(o[v.keys[i]], v, i+1)
)
fun prop2json(p) = p splitBy "\n"
map ($ splitBy "=")
map { keys: trim($[0]) splitBy ".", value: trim($[1]) }
reduce (v, a={}) -> field(a, v, 0)
---
prop2json(payload)

This is a one-step process, as DataWeave recognizes the YAML format. All you need to do is build the appropriate structure (prop2json function) and request output in "application/yaml" format. The function proceeds in two stages: it builds a structure where each key/value association is structured into as many objects associating an array of key segments and the value (this is the very beginning of the function's content) to obtain this:

[
{
"keys": [
"sftp",
"host"
],
"value": "localhost"
},
{
"keys": [
"sftp",
"port"
],
"value": "8081"
},
{
"keys": [
"salesforce",
"token"
],
"value": "fghhgghjhgffddgghh"
},
{
"keys": [
"salesforce",
"credentials",
"user"
],
"value": "Tintin"
},
{
"keys": [
"salesforce",
"credentials",
"password"
],
"value": "VGFDZE"
}
]

This formatting is followed by a reduction that builds the tree structure, using a recursive "field" function. The outer "if" of the field function separates the case where the segment encountered is not already present in the structure, from the case where it is. The inner "if" separates the case where the encountered value is of primitive type from the case where it is an object (which must be processed in a new step of the recursion). 



Aucun commentaire:

Enregistrer un commentaire

Pourquoi ce blog ? / Why this blog?

Mulesoft est un ESB du monde Salesforce utilisé pour construire des flots permettant aux pièces logicielles d'un Système d'Informati...