Boucle récursivement à travers les objets de l'objet

Aug 17 2020

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

1 traktor Aug 17 2020 at 07:30

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é .mappour .forEachpuisque 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'));

1 DavidNithaelTorresLima Aug 17 2020 at 07:18

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
    }, []);
}
1 JoeSeifi Aug 17 2020 at 07:49

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'));
1 Thankyou Aug 17 2020 at 08:02

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, keySearchest 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)

1 ScottSauyet Aug 18 2020 at 00:28

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é:

  • makeSimpleObjectprend 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}

  • makeSimpleObjectsfait 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}].

  • cstmTestest un prédicat simple pour tester si une clé commence (insensible à la casse) par "cstm".

  • et findAllDeepprend 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,, getNamesAndValuesutilise findAllDeep (cstmTest)pour trouver les valeurs correspondantes, puis makeSimpleObjects ('table', 'values')pour convertir le résultat au format final.

Notez que findAllDeep, makeSimpleObjectet makeSimpleObjectssont toutes les fonctions susceptibles d'être utiles ailleurs. La personnalisation ici n'est que dans cstmTestet dans la courte définition de getNamesAndValues. Je considérerais cela comme une victoire.