Bucle recursivamente a través de objetos de objeto

Aug 17 2020

Estoy tratando de escribir una función recursiva para pasar por un objeto y devolver elementos basados ​​en una ID. Puedo hacer que la primera parte de esto funcione, pero me está costando mucho intentar obtener esta función de manera recursiva y me vendría bien un par de ojos nuevos. El código está a continuación. Cuando ejecuta el fragmento, obtiene una matriz de 6 elementos que para la primera iteración es lo que quiero, pero ¿cómo puedo llamar a mi función con los parámetros adecuados para obtener los elementos anidados? Mi objetivo final es tener todos los objetos que comienzan con 'Cstm', anidados también, para agregarlos a la matriz tablesAndValues. Estaba tratando de modelar mi código después de esto: obtener todos los valores clave de JavaScript de matriz anidada de varios niveles , pero esto trata con una matriz de objetos y no con un objeto de objetos. Cualquier sugerencia o consejo que pueda obtener es muy apreciado.

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'));

Respuestas

1 traktor Aug 17 2020 at 07:30

Para lograr el resultado con una sola pulsación, se puede pasar la tabla de resultados a la función cuando se llama de forma recursiva, pero por defecto a una tabla vacía en la primera llamada. También cambié .mapa .forEachporque el valor de retorno no se usa:

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

Solo necesita llamar a push to tablesAndValues ​​dentro de la declaración else con el operador rest y pasar el valor y la identificación como parámetros

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'));

O de manera más corta

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

Aquí tienes una versión funcional. Llamo a la función principal de forma recursiva si el valor es una matriz o un objeto. También pasando en el estado actual de la matriz de conteo cada vez.

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

Aquí hay otro enfoque que usa generadores:

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' }
  }
]

Arriba, keySearches simplemente una especialización de filter:

function* filter (t = [], test = v => v)
{ for (const v of traverse(t)){
    if (test(v))
      yield v
  }
}

Que es una especialización de traverse-

function* traverse (t = {})
{ if (Object(t) === t)
    for (const [ k, v ] of Object.entries(t))
      ( yield [ k, v ]
      , yield* traverse(v)
      )
}

Expanda el fragmento a continuación para verificar el resultado en su navegador:

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

Una respuesta recursiva razonablemente elegante podría verse así:

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}

Pero esto no es tan simple como me gustaría. Este código mezcla la búsqueda de coincidencias y la prueba utilizada en esa búsqueda junto con el formato de la salida. Significa que es una función personalizada que es más compleja de entender y menos reutilizable de lo que me gustaría.

Preferiría usar algunas funciones reutilizables, separando tres características de esta funcionalidad. Entonces, si bien lo siguiente implica más líneas de código, creo que tiene más sentido:

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}

Todas estas son funciones auxiliares de diferente grado de reutilización:

  • makeSimpleObjecttoma dos nombres de clave, digamos 'foo', y 'bar'y devuelve una función que toma una matriz de dos elementos, digamos [10, 20]y devuelve un objeto que coincide con esos, como{foo: 10, bar: 20}

  • makeSimpleObjectshace lo mismo para una serie de matrices de dos elementos: makeSimpleObjects('foo', 'bar')([[8, 6], [7, 5], [30, 9]]) //=> [{foo: 8, bar: 6}, {foo: 7, bar: 5}, {foo: 30, bar: 9}].

  • cstmTestes un predicado simple para probar si una clave comienza (sin distinción entre mayúsculas y minúsculas) con "cstm".

  • y findAllDeeptoma un predicado y devuelve una función que toma un objeto y devuelve una matriz de matrices de dos elementos, que contiene los pares claves / valor para cualquier elemento que coincida con el predicado. (El predicado se proporciona tanto la clave como el valor; en el caso actual solo necesitamos la clave, pero parece sensato que la función tome cualquiera.

Nuestra función principal getNamesAndValues, utiliza findAllDeep (cstmTest)para encontrar los valores coincidentes y luego makeSimpleObjects ('table', 'values')convertir el resultado al formato final.

Tenga en cuenta que findAllDeep, makeSimpleObjecty makeSimpleObjectses probable que todas las funciones sean útiles en otros lugares. La personalización aquí es solo en cstmTesty en la definición corta de getNamesAndValues. Contaría eso como una victoria.