1
votes

Comment désemboîter récursivement des objets profondément imbriqués?

Je travaille avec un tableau qui pourrait avoir un certain nombre d'objets / tableaux d'imbrication imprévisible . Chacun des objets aura toujours une propriété name , et certains de ces objets peuvent avoir un tableau de sub_fields . Je souhaite dépouiller l'objet pour que la clé de chaque objet soit égale à la propriété name , tout en conservant les niveaux d'imbrication de l'original.

Voici un exemple de ce avec quoi je pourrais commencer:

var parsed_field_group = JSON.parse(JSON.stringify(field_group));
var fields = field_group[0].fields;

function removeKeys(obj, keys) {
    for (var prop in obj) {
        if(obj.hasOwnProperty(prop)) {
            switch(typeof(obj[prop])) {
                case 'object':
                    if(keys.indexOf(prop) > -1) {
                        delete obj[prop];
                    } else {
                        removeKeys(obj[prop], keys);
                    }
                    break;
                default:
                    if(!keys.includes(prop)) {
                        delete obj[prop];
                    }
                    break;
            }
        }
    }
}

removeKeys(fields, ['name', 'wrapper'])

console.log(fields)

var newJson = Object.create(null);
fields.forEach(field => {
    if (field.hasOwnProperty('sub_fields')) {
        newJson[field.name] = field
    } else {
        newJson[field.name] = null
    }
})

function handleSubFields(obj, key) {
    for (var prop in obj) {
        if (obj[prop]) {
            obj[prop][key].forEach(field => {
                if (field.hasOwnProperty(key)) {
                    obj[prop][field.name] = field
                } else {
                    obj[prop][field.name] = null
                }
            })
            delete obj[prop][key]
            delete obj[prop].name
        }
    }
}
handleSubFields(newJson, 'sub_fields')

console.log(newJson)

Idéalement, je vise à prendre ces données originales et à créer quelque chose comme ceci: p >

var newData = {
    'Object 1': null,
    'Object 2': null,
    'Object 3': {
        'Sublevel Object 1': {
            'Sublevel Object 1': null
        },
        'Sublevel Object 2': {
            'Sublevel Object 1': null,
            'Sublevel Object 2': null,
            'Sublevel Object 3': {
                  'Sublevel Object 1': null
             }
        }
    }
}

J'ai l'impression de passer beaucoup trop de temps là-dessus alors que je sais qu'il doit y avoir une sorte de fonction récursive pour y arriver --- je suis juste un peu trop inexpérimenté avec le genre de chose que j'ai du mal à trouver la solution.

Quelqu'un peut-il me montrer ce que je peux faire?

MODIFIER Voici ce que j'ai essayé. C'est un désordre confus et beaucoup trop fragmentaire - embarrassant en fait haha ​​- mais, de bonne foi, je ne voulais pas que vous pensiez que je n'ai pas essayé de comprendre cela par moi-même:

(également, le "field_group" en cours d'analyse provient d'une donnée JSON plus pertinente avec laquelle je travaille ... pas de l'exemple "foo / bar" ci-dessus.)

var data = [
    {
        foo: 'foo',
        bar: 'bar',
        name: 'Object 1'
    },
    {
        foo: 'foo',
        bar: 'bar',
        name: 'Object 2'
    },
    {
        foo: 'foo',
        bar: 'bar',
        name: 'Object 3',
        sub_fields : [
            {
                foo: 'foo',
                bar: 'bar',
                name: 'SubLevel Object 1',
                sub_fields: [
                    {
                        foo: 'foo',
                        bar: 'bar',
                        name: 'SubLevel Object 1',
                    }
                ]
            },
            {
                foo: 'foo',
                bar: 'bar',
                name: 'SubLevel Object 2',
                sub_fields: [
                    {
                        foo: 'foo',
                        bar: 'bar',
                        name: 'SubLevel Object 1',
                    },
                    {
                        foo: 'foo',
                        bar: 'bar',
                        name: 'SubLevel Object 2',
                    },
                    {
                        foo: 'foo',
                        bar: 'bar',
                        name: 'SubLevel Object 3',
                        sub_fields: [
                            {
                                foo: 'foo',
                                bar: 'bar',
                                name: 'SubLevel Object 1'
                            }
                        ]
                    }
                ]
            }
        ]
    }
]


16 commentaires

S'il ne s'agit pas d'une chaîne, ce n'est pas JSON


Pouvez-vous nous montrer ce que vous avez essayé? Cela n'a pas à fonctionner ou à être complet, nous aimons juste voir d'abord un effort de bonne foi. Et Andreas a raison, ce que vous avez dans la question n'est pas JSON, vous avez une expression littérale d'objet.


J'ai écrit une solution et j'ai remarqué que votre résultat attendu est étrange pour "Sublevel Object 3": null , car l'entrée contient un sub_fields .


@Andreas Si ce n'est pas une chaîne, ce n'est pas JSON Vous voudrez peut-être clarifier cette déclaration. Le booléen et les nombres sont des entités JSON valides par exemple


@Amy J'ai édité ma question / demande ci-dessus pour vous montrer ce que j'ai essayé.


@ASDFGerte bonne prise. J'ai fait le montage.


Ok, voici le code qui m'a fait remarquer ceci: const t = o => o.reduce ((p, c) => (p [c.name] = c.sub_fields? T (c.sub_fields) : null, p), {}); (peut échouer pour les cas extrêmes, n'a pas tout pris en compte)


@customcommander "JSON est une syntaxe de texte qui facilite l'échange de données structurées entre tous les langages de programmation. ... La syntaxe JSON décrit une séquence de points de code Unicode ." < / i> ( ECMA-404 )


@Andreas Je ne comprends peut-être pas alors mais vous semblez laisser entendre que {"foo": true} n'est pas un JSON valide, même s'il l'est. Peut-être confondez-vous la notation et la représentation sous forme de chaîne renvoyée par JSON.stringify ?


@customcommander JSON.parse ({toString: () => ({})}); . Oui, c'est exactement le cas. {"foo": true} n'est pas un json valide (en tant qu'objet javascript). JSON doit être une chaîne. JSON.parse effectue simplement un ToString automatique sur l'argument comme première étape, car javascribbidies aime les conversions implicites.


@customcommander JSON.stringify () (c'est même dans le nom de la propriété) renvoie JSON . JSON.parse () convertit une chaîne (qui doit être JSON valide) en types de données JavaScript réels (par exemple, des nombres, des booléens, des chaînes, des objets, ...). Et nous revenons à mon premier commentaire: tout ce qui n'est pas une chaîne n'est pas JSON. {"foo": true} // objet , "{\" foo \ ": true}" // chaîne qui est également JSON valide


Remarque: JSON.parse ({"foo": true}) échoue évidemment aussi, car "[object Object]" est un json invalide. C'était juste pour montrer que JSON.parse essaie d'abord de se convertir en chaîne, car JSON doit être une chaîne.


@Andreas Je ne suis pas nécessairement en désaccord, mais je ne suis pas sûr que ce soit l'histoire complète. Que faites-vous de cette déclaration sur MDN alors: array est également JSON valide Voir developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JS‌ ON


@customcommander Ils ne font pas référence aux éléments de la structure de données. Ils signifient que tout est un contenu de chaîne. Si vous avez un fichier appelé data.json qui contient des données JSON, ce fichier entier est considéré comme une chaîne pour les commentaires d'Andrea, car le fichier lui-même est du contenu textuel et non binaire. Lorsque ce fichier est lu dans le code, ce sera une chaîne qui peut ensuite être analysée dans une structure d'objet JS correspondante.


@Amy Ah bon je vois, assez bien. Je pense que la plupart des gens reconnaîtraient que {"foo": true} dans un fichier json est dans un format textuel et non dans un format binaire. D'où pourquoi j'étais confus. Mais cela vient d'un point de vue pratique, point néanmoins pris.


@customcommander Oui, dire "JSON est une chaîne" n'est pas entièrement exact; plus précis serait "JSON est un texte contenant une structure bien définie". Nous disons que c'est une chaîne car, lorsque vous travaillez avec elle dans n'importe quel langage de programmation, elle est une chaîne. Les expressions littérales d'objet sont couramment et incorrectement appelées JSON, mais comme elles ne sont pas une chaîne, il ne s'agit pas en fait de JSON. Dans le cas de cette question, les clés d'objet ne sont pas entre guillemets, ce qui est requis pour JSON, mais pas pour les expressions littérales d'objet. JSON est littéralement la représentation sous forme de chaîne d'un objet JS.


4 Réponses :


0
votes

Oui, c'est très simple avec la récursivité. Voici votre cas de base:

  • Vous recevez quelque chose qui n'est pas un objet - vous renvoyez null .

Ensuite, votre cas de récursivité de base:

  • Vous recevez un objet, vous construisez un nouvel objet avec une propriété le nom de l'objet, la valeur étant l'application récursive de la fonction.

Enfin, il vous suffit de gérer ce qui se passe si vous avez des tableaux:

  • Parcourez tout le tableau et appelez la fonction sur chaque élément. Rassemblez tous les résultats dans le même objet.

var data = [{ foo: 'foo', bar: 'bar', name: 'Object 1' }, { foo: 'foo', bar: 'bar', name: 'Object 2' }, { foo: 'foo', bar: 'bar', name: 'Object 3', sub_fields: [{ foo: 'foo', bar: 'bar', name: 'SubLevel Object 1', sub_fields: [{ foo: 'foo', bar: 'bar', name: 'SubLevel Object 1', }] }, { foo: 'foo', bar: 'bar', name: 'SubLevel Object 2', sub_fields: [{ foo: 'foo', bar: 'bar', name: 'SubLevel Object 1', }, { foo: 'foo', bar: 'bar', name: 'SubLevel Object 2', }, { foo: 'foo', bar: 'bar', name: 'SubLevel Object 3', sub_fields: [{ foo: 'foo', bar: 'bar', name: 'SubLevel Object 1' }] } ] } ] } ]


function toObj(data) {
  if (data == null) return null;
  
  //collect all recursive calls to the function in the same object
  if (Array.isArray(data))
    return data.reduce((acc, data) => ({ ...acc, ...toObj(data)}), {})
  
  //produce a new object from the `name` property and a recursive application of the function
  return { [data.name]: toObj(data.sub_fields, {}) };
}

console.log(toObj(data))


1 commentaires

Merci beaucoup. C'est beaucoup plus élégant et concis que ce que j'essayais. Prouve que j'ai BEAUCOUP à apprendre. Je vous suis vraiment reconnaissant d'avoir pris le temps de nous aider.



2
votes

Pour transformer

<script>
const data = [{"foo":"foo","bar":"bar","name":"Object 1"},{"foo":"foo","bar":"bar","name":"Object 2"},{"foo":"foo","bar":"bar","name":"Object 3","sub_fields":[{"foo":"foo","bar":"bar","name":"SubLevel Object 1","sub_fields":[{"foo":"foo","bar":"bar","name":"SubLevel Object 1"}]},{"foo":"foo","bar":"bar","name":"SubLevel Object 2","sub_fields":[{"foo":"foo","bar":"bar","name":"SubLevel Object 1"},{"foo":"foo","bar":"bar","name":"SubLevel Object 2"},{"foo":"foo","bar":"bar","name":"SubLevel Object 3","sub_fields":[{"foo":"foo","bar":"bar","name":"SubLevel Object 1"}]}]}]}];
</script>

en:

const transform =
  ({name, sub_fields}) =>
    ( { [name]: sub_fields
          ? Object.assign({}, ...sub_fields.map(transform))
          : null
      }
    );
    
    
console.log(

  Object.assign({}, ...data.map(transform))

);

Vous pouvez utiliser cette fonction:

const transform =
  ({name, sub_fields}) =>
    ( { [name]: sub_fields
          ? Object.assign({}, ...sub_fields.map(transform))
          : null
      }
    );


1 commentaires

Une bonne raison pour {[name]:! Sub_fields? null: Object.assign ({}, ... sub_fields.map (transform))} over {[name]: sub_fields? Object.assign ({}, ... sub_fields.map (transformation)): null} . Je trouve le ! plus difficile à remarquer.



0
votes

Je développe cette fonction en utilisant la récursivité pour atteindre votre objectif, sachant que dans votre objet il y aura toujours un attribut appelé 'nom', et parfois d'autres appelés 'sous-champs'.

function rebuildNameObject(objArr = [], resObj = {}){
  objArr.forEach(obj => {
        Object.keys(obj).forEach(item => {
          if (item === 'name'){
            resObj[obj[item]] = null;
          }

          if(item === 'sub_fields'){
            resObj[obj['name']] = rebuildNameObject(obj[item]);
          }

        });
    });
  return resObj;
}


0 commentaires

1
votes

Il y a déjà plusieurs bonnes réponses et une belle exposition de customcommander. Je pense que cette alternative mérite d'être prise en considération pour plusieurs raisons:

  • Je choisis réduire uniquement si quelque chose de plus explicite n'est pas disponible. Bien que ce soit la plus puissante des méthodes d'itération de tableau, c'est un niveau inférieur et pour moi ce n'est pas aussi auto-documenté que cette combinaison de map et Object.assign . (Ceci est encore plus important par rapport à forEach , qui n'a aucune sémantique inhérente.)

  • J'ai une préférence personnelle pour travailler avec des expressions plutôt qu'avec des instructions, et je préfère une fonction de flèche à expression unique à une avec une ou plusieurs instructions return .

    li >
  • Bien que l'exposition de customcommander soit excellente et que j'aime beaucoup la technique, la fonction transform a encore besoin d'un wrapper pour obtenir le résultat attendu. Il n'y a absolument rien de mal avec les wrappers (même si j'ajouterais une autre fonction pour cela.) Mais la version que je propose ici gère la même chose avec des techniques similaires et sans complexité supplémentaire sans avoir besoin d'un wrapper.

const transform = (obj) =>
  Array .isArray (obj)
    ? Object .assign (...obj .map (transform))
    : {[obj .name] : 'sub_fields' in obj ? transform (obj .sub_fields) : null}

const data = [{ foo: 'foo', bar: 'bar', name: 'Object 1' }, { foo: 'foo', bar: 'bar', name: 'Object 2' }, { foo: 'foo', bar: 'bar', name: 'Object 3', sub_fields: [{ foo: 'foo', bar: 'bar', name: 'SubLevel Object 1', sub_fields: [{ foo: 'foo', bar: 'bar', name: 'SubLevel Object 1', }] }, { foo: 'foo', bar: 'bar', name: 'SubLevel Object 2', sub_fields: [{ foo: 'foo', bar: 'bar', name: 'SubLevel Object 1', }, { foo: 'foo', bar: 'bar', name: 'SubLevel Object 2', }, { foo: 'foo', bar: 'bar', name: 'SubLevel Object 3', sub_fields: [{ foo: 'foo', bar: 'bar', name: 'SubLevel Object 1' }] } ] } ] } ]

console .log (transform (data))

Je ne veux pas suggérer qu'il y a quelque chose de mal avec les autres solutions, seulement que cette combinaison de facteurs signifiait qu'il y avait de la place pour une autre technique.


2 commentaires

Ne muteriez-vous pas le premier élément de ... obj.map (transform) si vous ne fusionnez pas d'abord dans un objet vide?


Ce serait le cas, mais c'est quand même un objet jetable.