O ponto chave nesse desafio é encontrar uma forma de pesquisar por cada cenário onde é possível encontrar uma "connection", seja ele um array, um objeto ou qualquer outro, e ainda fazer isso de forma contínua e acessando propriedades mais aninhadas. Uma forma de obter isso é através de recursão, permitindo uma solução simples e legível para continuamente executar o procedimento de busca em um objeto.
Para começar, precisamos de uma forma de armazenar os resultados e um ponto de partida de onde vamos começar a nossa função recursiva. O resultado pode ser armazenado um array fora da função. Como o enunciado nos garante que recebemos um objeto, podemos começar lidando com as propriedades desse objeto. Para fazer isso utilizamos o Object.entries() para obter seu conteúdo em forma de um array de tuplas e então encadeamos com um .forEach() para percorrermos todo o conteúdo tendo acesso à chave da propriedade na posição 0 e também ao seu valor na posição 1.
const results = []
function searchConnections(data) {
Object.entries(data).forEach(tuple => {
const [key, value] = tuple
})
}
Agora que estamos acessando as propriedades do objeto precisamos pensar em todos os tipos de valor que podemos encontrar nelas. O primeiro que podemos tratar é a própria chave "connection", que seria o caso mais básico possível. Se estamos lidando com um objeto "connection" tudo que precisamos fazer é usar o .push() para salvar o seu "_id" e a sua "label" no nosso array de resultados.
const results = []
function searchConnections(data) {
Object.entries(data).forEach(tuple => {
const [key, value] = tuple
if (key === 'connection') {
results.push([value._id, value.label])
}
})
}
O segundo caso também bem simples que podemos encontrar é um array identificado como "connections", que será um array contendo objetos que podemos considerar uma "connection", ou seja, de onde devemos buscar as propriedades "_id" e "label". Vamos então iterar sobre o valor da chave "connections" e usar um .push() para armazenar esses resultados em nosso array de resultados.
const results = []
function searchConnections(data) {
Object.entries(data).forEach(tuple => {
const [key, value] = tuple
if (key === 'connection') {
results.push([value._id, value.label])
}
if (key === 'connections') {
value.forEach(connection=> {
results.push([connection._id, connection.label])
})
}
})
}
Passando para o próximo caso, se a chave não for um array chamado "connections" vamos verificar se ela é um outro array qualquer, afinal outros arrays podem possuir objetos mais aninhados que possuem uma "connection" ou até mesmo "connections". Aqui a recursão entra em cena, pois para cada objeto dentro desse array de conteúdo qualquer podemos chamar a nossa própria função novamente, pesquisando por resultados mais aninhados. A forma que usaremos para verificar se a propriedade atual é um array é usando método Array.isArray() passando o seu valor.
const results = []
function searchConnections(data) {
Object.entries(data).forEach(tuple => {
const [key, value] = tuple
if (key === 'connection') {
results.push([value._id, value.label])
}
if (key === 'connections') {
value.forEach(connection=> {
results.push([connection._id, connection.label])
})
}
if (Array.isArray(value)) {
value.forEach(innerObject => {
searchConnections(innerObject)
})
}
})
}
Agora também precisamos considerar o caso onde a propriedade atual é um objeto qualquer que não é uma "connection", mas pode conter outros objetos ou arrays que contém "connection" ou "connections". Mas para verificar se estamos lidando com um objeto Object de fato precisamos utilizar algo um pouco diferente de um mero typeof === 'object'. Isso porque o javascript identificará como tendo o tipo 'object' coisas como arrays e null, portanto podemos checar de forma mais segura por um objeto convertendo-o para string, o que deverá retornar 'object [Object]'. Caso estejamos lidando com uma propriedade que é um objeto qualquer podemos simplesmente chamar nossa própria função passando o seu valor, assim iremos continuamente pesquisar por "connections" mais internas a esse objeto. Vejamos como ficaria o nosso código:
const results = []
function searchConnections(data) {
Object.entries(data).forEach(tuple => {
const [key, value] = tuple
if (key === 'connection') {
results.push([value._id, value.label])
}
if (key === 'connections') {
value.forEach(component => {
results.push([component._id, component.label])
})
}
if (Array.isArray(value)) {
value.forEach(innerObject => {
searchConnections(innerObject)
})
} else if (Object.prototype.toString.call(value) === '[object Object]') {
searchConnections(value)
}
})
}
Com isso nosso código já deve funcionar corretamente, mas ainda podemos tirar vantagem de alguns recursos do javascript para deixá-lo mais limpo, como a desestruturação da tupla na própria declaração de parâmetros.
const results = []
function searchConnections(data) {
Object.entries(data).forEach( ([key, value]) => {
if (key === 'connection') {
results.push([value._id, value.label])
}
if (key === 'connections') {
value.forEach(connection => {
results.push([connection._id, connection.label])
})
}
if (Array.isArray(value)) {
value.forEach(innerObject => {
searchConnections(innerObject)
})
} else if (Object.prototype.toString.call(value) === '[object Object]') {
searchConnections(value)
}
})
}