Boucle récursivement à travers les objets de l'objet
J'essaye d'écrire une fonction récursive pour passer par un objet et retourner des éléments basés sur un identifiant. Je peux faire fonctionner la première partie de cela, mais j'ai du mal à essayer d'obtenir cette fonction de manière récursive et je pourrais utiliser un nouvel œil. Le code est ci-dessous. Lorsque vous exécutez l'extrait, vous obtenez un tableau de 6 éléments qui, pour la première itération, est ce que je veux, mais comment puis-je appeler ma fonction avec les paramètres appropriés pour obtenir les éléments imbriqués? Mon objectif final est d'avoir tous les objets commençant par 'Cstm', imbriqués aussi, à ajouter au tableau tablesAndValues. J'essayais de modéliser mon code après ceci: obtenir toutes les valeurs de clé à partir de JavaScript de tableau imbriqué à plusieurs niveaux , mais cela concerne un tableau d'objets et non un objet d'objets. Tous les conseils ou astuces que je peux obtenir sont très appréciés.
JSFiddle: https://jsfiddle.net/xov49jLs/
const response = {
"data": {
"Cstm_PF_ADG_URT_Disposition": {
"child_welfare_placement_value": ""
},
"Cstm_PF_ADG_URT_Demographics": {
"school_grade": "family setting",
"school_grade_code": ""
},
"Cstm_Precert_Medical_Current_Meds": [
{
"med_name": "med1",
"dosage": "10mg",
"frequency": "daily"
},
{
"med_name": "med2",
"dosage": "20mg",
"frequency": "daily"
}
],
"Cstm_PF_ADG_URT_Substance_Use": {
"dimension1_comment": "dimension 1 - tab1",
"Textbox1": "text - tab1"
},
"Cstm_PF_ADG_Discharge_Note": {
"prior_auth_no_comm": "auth no - tab2"
},
"Cstm_PF_ADG_URT_Clinical_Plan": {
"cca_cs_dhs_details": "details - tab2"
},
"container": {
"Cstm_PF_Name": {
"first_name": "same text for textbox - footer",
"last_name": "second textbox - footer"
},
"Cstm_PF_ADG_URT_Demographics": {
"new_field": "mapped demo - footer"
},
"grid2": [
{
"Cstm_PF_ADG_COMP_Diagnosis": {
"diagnosis_label": "knee",
"diagnosis_group_code": "leg"
}
},
{
"Cstm_PF_ADG_COMP_Diagnosis": {
"diagnosis_label": "ankle",
"diagnosis_group_code": "leg"
}
}
]
},
"submit": true
}
};
function getNamesAndValues(data, id) {
const tablesAndValues = [],
res = data;
Object.entries(res).map(([key, value]) => {
const newKey = key.split('_')[0].toLowerCase();
// console.log(newKey) // -> 'cstm'
if (newKey === id) {
tablesAndValues.push({
table: key,
values: value
});
} else {
// I can log value and key and see what I want to push
// to the tablesAndValues array, but I can't seem to get
// how to push the nested items.
// console.log(value);
// console.log(key);
// getNamesAndValues(value, key)
}
});
return tablesAndValues;
}
console.log(getNamesAndValues(response.data, 'cstm'));
Réponses
Pour obtenir le résultat en un seul clic, on peut passer la table de résultats à la fonction lorsqu'elle est appelée de manière récursive, mais par défaut à une table vide lors du premier appel. J'ai aussi changé .map
pour .forEach
puisque la valeur de retour n'est pas utilisé:
const response = {
"data": {
"Cstm_PF_ADG_URT_Disposition": {
"child_welfare_placement_value": ""
},
"Cstm_PF_ADG_URT_Demographics": {
"school_grade": "family setting",
"school_grade_code": ""
},
"Cstm_Precert_Medical_Current_Meds": [
{
"med_name": "med1",
"dosage": "10mg",
"frequency": "daily"
},
{
"med_name": "med2",
"dosage": "20mg",
"frequency": "daily"
}
],
"Cstm_PF_ADG_URT_Substance_Use": {
"dimension1_comment": "dimension 1 - tab1",
"Textbox1": "text - tab1"
},
"Cstm_PF_ADG_Discharge_Note": {
"prior_auth_no_comm": "auth no - tab2"
},
"Cstm_PF_ADG_URT_Clinical_Plan": {
"cca_cs_dhs_details": "details - tab2"
},
"container": {
"Cstm_PF_Name": {
"first_name": "same text for textbox - footer",
"last_name": "second textbox - footer"
},
"Cstm_PF_ADG_URT_Demographics": {
"new_field": "mapped demo - footer"
},
"grid2": [
{
"Cstm_PF_ADG_COMP_Diagnosis": {
"diagnosis_label": "knee",
"diagnosis_group_code": "leg"
}
},
{
"Cstm_PF_ADG_COMP_Diagnosis": {
"diagnosis_label": "ankle",
"diagnosis_group_code": "leg"
}
}
]
},
"submit": true
}
};
function getNamesAndValues(data, id, tablesAndValues = []) {
const res = data;
Object.entries(res).forEach(([key, value]) => {
const newKey = key.split('_')[0].toLowerCase();
if (newKey === id) {
tablesAndValues.push({
table: key,
values: value
});
} else {
getNamesAndValues( value, id, tablesAndValues); }
});
return tablesAndValues;
}
console.log(getNamesAndValues(response.data, 'cstm'));
Il vous suffit d'appeler push to tablesAndValues dans l'instruction else avec l'opérateur rest et de transmettre la valeur et l'id en tant que paramètres
const response = {
"data": {
"Cstm_PF_ADG_URT_Disposition": {
"child_welfare_placement_value": ""
},
"Cstm_PF_ADG_URT_Demographics": {
"school_grade": "family setting",
"school_grade_code": ""
},
"Cstm_Precert_Medical_Current_Meds": [
{
"med_name": "med1",
"dosage": "10mg",
"frequency": "daily"
},
{
"med_name": "med2",
"dosage": "20mg",
"frequency": "daily"
}
],
"Cstm_PF_ADG_URT_Substance_Use": {
"dimension1_comment": "dimension 1 - tab1",
"Textbox1": "text - tab1"
},
"Cstm_PF_ADG_Discharge_Note": {
"prior_auth_no_comm": "auth no - tab2"
},
"Cstm_PF_ADG_URT_Clinical_Plan": {
"cca_cs_dhs_details": "details - tab2"
},
"container": {
"Cstm_PF_Name": {
"first_name": "same text for textbox - footer",
"last_name": "second textbox - footer"
},
"Cstm_PF_ADG_URT_Demographics": {
"new_field": "mapped demo - footer"
},
"grid2": [
{
"Cstm_PF_ADG_COMP_Diagnosis": {
"diagnosis_label": "knee",
"diagnosis_group_code": "leg"
}
},
{
"Cstm_PF_ADG_COMP_Diagnosis": {
"diagnosis_label": "ankle",
"diagnosis_group_code": "leg"
}
}
]
},
"submit": true
}
};
function getNamesAndValues(data, id) {
const tablesAndValues = [],
res = data;
Object.entries(res).map(([key, value]) => {
const newKey = key.split('_')[0].toLowerCase();
// console.log(newKey) // -> 'cstm'
if (newKey === id) {
tablesAndValues.push({
table: key,
values: value
});
} else {
// I can log value and key and see what I want to push
// to the tablesAndValues array, but I can't seem to get
// how to push the nested items.
// console.log(value);
// console.log(key);
tablesAndValues.push(...getNamesAndValues(value, id))
}
});
return tablesAndValues;
}
console.log(getNamesAndValues(response.data, 'cstm'));
Ou de manière plus courte
function getNamesAndValues2(data, id) {
return Object.entries(data).reduce((arr, [key, value]) => {
arr.push(
...(key.split('_')[0].toLowerCase() === id ? [{ table: key, values: value }] : getNamesAndValues(value, id))
);
return arr
}, []);
}
Voici une version de travail. J'appelle la fonction main de manière récursive si la valeur est un tableau ou un objet. Passer également à chaque fois l'état actuel du tableau de pointage.
function getNamesAndValues(data, id, tablesAndValues = []) {
const res = data;
Object.entries(res).map(([key, value]) => {
const newKey = key.split('_')[0].toLowerCase();
const item = res[key];
if (newKey === id) {
tablesAndValues.push({
table: key,
values: value
});
}
if(Array.isArray(item)) {
return item.map(el => getNamesAndValues(el, id, tablesAndValues));
}
if(typeof item === 'object') {
return getNamesAndValues(item, id, tablesAndValues);
}
})
return tablesAndValues;
}
console.log(getNamesAndValues(response.data, 'cstm'));
Voici une autre approche utilisant des générateurs -
const keySearch = (t = [], q = "") =>
filter(t, ([ k, _ ]) => String(k).startsWith(q))
const r =
Array.from
( keySearch(response, "Cstm")
, ([ table, values ]) =>
({ table, values })
)
console.log(r)
[
{
table: 'Cstm_PF_ADG_URT_Disposition',
values: { child_welfare_placement_value: '' }
},
{
table: 'Cstm_PF_ADG_URT_Demographics',
values: { school_grade: 'family setting', school_grade_code: '' }
},
{
table: 'Cstm_Precert_Medical_Current_Meds',
values: [ [Object], [Object] ]
},
{
table: 'Cstm_PF_ADG_URT_Substance_Use',
values: {
dimension1_comment: 'dimension 1 - tab1',
Textbox1: 'text - tab1'
}
},
{
table: 'Cstm_PF_ADG_Discharge_Note',
values: { prior_auth_no_comm: 'auth no - tab2' }
},
{
table: 'Cstm_PF_ADG_URT_Clinical_Plan',
values: { cca_cs_dhs_details: 'details - tab2' }
},
{
table: 'Cstm_PF_Name',
values: {
first_name: 'same text for textbox - footer',
last_name: 'second textbox - footer'
}
},
{
table: 'Cstm_PF_ADG_URT_Demographics',
values: { new_field: 'mapped demo - footer' }
},
{
table: 'Cstm_PF_ADG_COMP_Diagnosis',
values: { diagnosis_label: 'knee', diagnosis_group_code: 'leg' }
},
{
table: 'Cstm_PF_ADG_COMP_Diagnosis',
values: { diagnosis_label: 'ankle', diagnosis_group_code: 'leg' }
}
]
Ci-dessus, keySearch
est simplement une spécialisation de filter
-
function* filter (t = [], test = v => v)
{ for (const v of traverse(t)){
if (test(v))
yield v
}
}
Qui est une spécialisation de traverse
-
function* traverse (t = {})
{ if (Object(t) === t)
for (const [ k, v ] of Object.entries(t))
( yield [ k, v ]
, yield* traverse(v)
)
}
Développez l'extrait ci-dessous pour vérifier le résultat dans votre navigateur -
function* traverse (t = {})
{ if (Object(t) === t)
for (const [ k, v ] of Object.entries(t))
( yield [ k, v ]
, yield* traverse(v)
)
}
function* filter (t = [], test = v => v)
{ for (const v of traverse(t)){
if (test(v))
yield v
}
}
const keySearch = (t = [], q = "") =>
filter(t, ([ k, _ ]) => String(k).startsWith(q))
const response =
{"data":{"Cstm_PF_ADG_URT_Disposition":{"child_welfare_placement_value":""},"Cstm_PF_ADG_URT_Demographics":{"school_grade":"family setting","school_grade_code":""},"Cstm_Precert_Medical_Current_Meds":[{"med_name":"med1","dosage":"10mg","frequency":"daily"},{"med_name":"med2","dosage":"20mg","frequency":"daily"}],"Cstm_PF_ADG_URT_Substance_Use":{"dimension1_comment":"dimension 1 - tab1","Textbox1":"text - tab1"},"Cstm_PF_ADG_Discharge_Note":{"prior_auth_no_comm":"auth no - tab2"},"Cstm_PF_ADG_URT_Clinical_Plan":{"cca_cs_dhs_details":"details - tab2"},"container":{"Cstm_PF_Name":{"first_name":"same text for textbox - footer","last_name":"second textbox - footer"},"Cstm_PF_ADG_URT_Demographics":{"new_field":"mapped demo - footer"},"grid2":[{"Cstm_PF_ADG_COMP_Diagnosis":{"diagnosis_label":"knee","diagnosis_group_code":"leg"}},{"Cstm_PF_ADG_COMP_Diagnosis":{"diagnosis_label":"ankle","diagnosis_group_code":"leg"}}]},"submit":true}}
const result =
Array.from
( keySearch(response, "Cstm")
, ([ table, values ]) =>
({ table, values })
)
console.log(result)
Une réponse récursive raisonnablement élégante pourrait ressembler à ceci:
const getNamesAndValues = (obj) =>
Object (obj) === obj
? Object .entries (obj)
.flatMap (([k, v]) => [
... (k .toLowerCase () .startsWith ('cstm') ? [{table: k, value: v}] : []),
... getNamesAndValues (v)
])
: []
const response = {data: {Cstm_PF_ADG_URT_Disposition: {child_welfare_placement_value: ""}, Cstm_PF_ADG_URT_Demographics: {school_grade: "family setting", school_grade_code: ""}, Cstm_Precert_Medical_Current_Meds: [{med_name: "med1", dosage: "10mg", frequency: "daily"}, {med_name: "med2", dosage: "20mg", frequency: "daily"}], Cstm_PF_ADG_URT_Substance_Use: {dimension1_comment: "dimension 1 - tab1", Textbox1: "text - tab1"}, Cstm_PF_ADG_Discharge_Note: {prior_auth_no_comm: "auth no - tab2"}, Cstm_PF_ADG_URT_Clinical_Plan: {cca_cs_dhs_details: "details - tab2"}, container: {Cstm_PF_Name: {first_name: "same text for textbox - footer", last_name: "second textbox - footer"}, Cstm_PF_ADG_URT_Demographics: {new_field: "mapped demo - footer"}, grid2: [{Cstm_PF_ADG_COMP_Diagnosis: {diagnosis_label: "knee", diagnosis_group_code: "leg"}}, {Cstm_PF_ADG_COMP_Diagnosis: {diagnosis_label: "ankle", diagnosis_group_code: "leg"}}]}, submit: true}}
console .log (getNamesAndValues (response))
.as-console-wrapper {max-height: 100% !important; top: 0}
Mais ce n'est pas aussi simple que je le souhaiterais. Ce code mélange la recherche de correspondances et le test utilisé dans cette recherche avec le formatage de la sortie. Cela signifie qu'il s'agit d'une fonction personnalisée qui est à la fois plus complexe à comprendre et moins réutilisable que je ne le souhaiterais.
Je préférerais utiliser certaines fonctions réutilisables, en séparant trois caractéristiques de cette fonctionnalité. Donc, bien que ce qui suit implique plus de lignes de code, je pense que cela a plus de sens:
const findAllDeep = (pred) => (obj) =>
Object (obj) === obj
? Object .entries (obj)
.flatMap (([k, v]) => [
... (pred (k, v) ? [[k, v]] : []),
... findAllDeep (pred) (v)
])
: []
const makeSimpleObject = (name1, name2) => ([k, v]) =>
({[name1]: k, [name2]: v})
const makeSimpleObjects = (name1, name2) => (xs) =>
xs .map (makeSimpleObject (name1, name2))
const cstmTest = k =>
k .toLowerCase () .startsWith ('cstm')
const getNamesAndValues = (obj) =>
makeSimpleObjects ('table', 'values') (findAllDeep (cstmTest) (obj))
const response = {data: {Cstm_PF_ADG_URT_Disposition: {child_welfare_placement_value: ""}, Cstm_PF_ADG_URT_Demographics: {school_grade: "family setting", school_grade_code: ""}, Cstm_Precert_Medical_Current_Meds: [{med_name: "med1", dosage: "10mg", frequency: "daily"}, {med_name: "med2", dosage: "20mg", frequency: "daily"}], Cstm_PF_ADG_URT_Substance_Use: {dimension1_comment: "dimension 1 - tab1", Textbox1: "text - tab1"}, Cstm_PF_ADG_Discharge_Note: {prior_auth_no_comm: "auth no - tab2"}, Cstm_PF_ADG_URT_Clinical_Plan: {cca_cs_dhs_details: "details - tab2"}, container: {Cstm_PF_Name: {first_name: "same text for textbox - footer", last_name: "second textbox - footer"}, Cstm_PF_ADG_URT_Demographics: {new_field: "mapped demo - footer"}, grid2: [{Cstm_PF_ADG_COMP_Diagnosis: {diagnosis_label: "knee", diagnosis_group_code: "leg"}}, {Cstm_PF_ADG_COMP_Diagnosis: {diagnosis_label: "ankle", diagnosis_group_code: "leg"}}]}, submit: true}}
console .log (findAllDeep (cstmTest) (response))
.as-console-wrapper {max-height: 100% !important; top: 0}
Ce sont toutes des fonctions d'assistance de degré variable de réutilisabilité:
makeSimpleObject
prend deux noms de clé, disons'foo'
et'bar'
et renvoie une fonction qui prend un tableau à deux éléments, disons[10, 20]
et renvoie un objet correspondant à ceux-ci, comme{foo: 10, bar: 20}
makeSimpleObjects
fait la même chose pour un tableau de tableaux à deux éléments:makeSimpleObjects('foo', 'bar')([[8, 6], [7, 5], [30, 9]]) //=> [{foo: 8, bar: 6}, {foo: 7, bar: 5}, {foo: 30, bar: 9}]
.cstmTest
est un prédicat simple pour tester si une clé commence (insensible à la casse) par"cstm"
.et
findAllDeep
prend un prédicat et retourne une fonction qui prend un objet et renvoie un tableau de tableaux à deux éléments, contenant les paires clés / valeurs pour tous les éléments qui correspondent au prédicat. (Le prédicat est fourni à la fois la clé et la valeur; dans le cas actuel, nous n'avons besoin que de la clé, mais il semble judicieux que la fonction prenne l'un ou l'autre.
Notre fonction principale,, getNamesAndValues
utilise findAllDeep (cstmTest)
pour trouver les valeurs correspondantes, puis makeSimpleObjects ('table', 'values')
pour convertir le résultat au format final.
Notez que findAllDeep
, makeSimpleObject
et makeSimpleObjects
sont toutes les fonctions susceptibles d'être utiles ailleurs. La personnalisation ici n'est que dans cstmTest
et dans la courte définition de getNamesAndValues
. Je considérerais cela comme une victoire.