Ir para conteúdo
kania

Função genérica para atualizar chave em JSON duplicada usando o PHP

Recommended Posts

Tenho a seguinte situação.
Recebo vários JSONs podendo ou não ser multidimensional, preciso atualizar uma determinada chave deste JSON, o problema é que algumas chaves podem ser duplicadas, bem como seus respectivos valores. Como estou tentando criar uma função genérica para navegar em qualquer JSON e modificar a chave em si, com estas duplicidades de chaves, estou tendo dificuldades em dizer ao código qual é chave que devo alterar.

 

Arquivo JSON Exemplo

 

{
      "CREDITOR": {
        "TAX": {
          "TAC": 0.7
        },
        "ENABLE": "true",
        "PRODUCTION": {
          "email": "email@dominio.com",
          "senha": "12457895",
          "BASE_URL": "https://domino.com"
        },
        "HOMOLOGATION": {
          "email": "email@dominio.com",
          "senha": "12457895",
          "BASE_URL": "https://domino.com"
        },
        "TARGET_VALUES": 5000000
      }
    }

 

Converto o JSON para array

 

$json = json_decode($json_string, true);


Função que criei até aqui

 

   

/**
     * Encontra a chave correspondente dentro do JSON
     *
     * @param array $jsonArray - JSON a ser verificado
     * @param string $keyFather - chave de entrada
     * @param string $keyUpdate - chave que modificar
     * @param string $valueUpdate - novo valor da chave
     * @return string
     *
     */
    public static function searchKeyJson(array $jsonArray, string $keyFather, string $keyUpdate = null, $valueUpdate = null)
    {
        foreach ($jsonArray as $key => $value) {
            if ($key == $keyFather && $keyUpdate == null) {
                $jsonArray[$key] =  $valueUpdate;
                return $jsonArray;
            }
            if ($key == $keyFather && $keyUpdate != null) {
                $jsonArray[$keyFather][$keyUpdate] = $valueUpdate;
                return $jsonArray;
            }
            if (is_array($value)) {
                if (($result = self::searchKeyJson($value, $keyFather, $keyUpdate, $valueUpdate)) !== false) {
                    if ($keyUpdate == null) {
                        return $result;
                    } else {
                        return $result;
                    }
                }
            }
        }
        return false;
    }


Até modifico o valor, mais no final para salvar, ele não monta o JSON como original, ele caba ignorando a chave inicial no JSON de exmeplo "CREDITOR": {}

 

Retorno da função (notem que esta forma do padrão da original)

 

=> [
         "TAX" => [
           "TAC" => 0.7,
         ],
         "ENABLE" => "true",
         "PRODUCTION" => [
           "email" => "teste",
           "senha" => "12457895",
           "BASE_URL" => "https://domino.com",
         ],
         "HOMOLOGATION" => [
           "email" => "email@dominio.com",
           "senha" => "12457895",
           "BASE_URL" => "https://domino.com",
         ],
         "TARGET_VALUES" => 5000000,
       ]

 

Se eu percorrer o array e tentar modificar a chave em questão, ele muda todas as as chaves que tiverem no JSON porque tem duplicidade.

Como posso resolver isto com uma função global que sirva para qualquer padrão de JSON que eu tiver?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Olá amigo, tudo bem?

 

Vou tentar ajudar você, mas não entendi alguns pontos:

 

No seu exemplo houve alteração no e-mail apenas de PRODUCTION, mas não em HOMOLOGATION. É esse o comportamento esperado?

Desculpe, mas não entendi muito bem seu objetivo: seria varrer todo o array pela chave 'email' (por exemplo) e substituir todas as ocorrências ou apenas a primeira encontrada (ou ainda outra coisa)?

 

Apenas para ilustrar melhor, como você fez a chamada da sua função para que ela gerasse o resultado que você colou no seu post? Digo, quais parâmetros você passou pra dentro dela?

 

Apenas uma observação:

                    if ($keyUpdate == null) {
                        return $result;
                    } else {
                        return $result;
                    }

É o mesmo que simplesmente:

return $result;

(Provavelmente foi apenas falta de atenção, mas não custa mencionar...)

Compartilhar este post


Link para o post
Compartilhar em outros sites

Olá noite Mateus, obrigado por responder.

 

A citação do erro estava correta, dupliquei o retorno do return $result (hehehe)

 

Vamos lá, vou detalhar melhor o que estou tentando fazer.

 

A aplicação Backend esta em uma API em Laravel 9, com banco de dados Mysql e o Front em Vue.js 3.

No banco de dados, existem várias tabelas com colunas do tipo jsonb.

 

O que preciso fazer é manipular este campo jsonb no PHP, para isto pensei no seguinte.

 

- Ter uma função genérica já que estes campos jsonb não tem um json igual em cada;

- A função precisa poder percorrer o json (já convertido em array para trabalhar no php) e encontrara chave especifica para poder modifica-la

 

No exemplo acima, eu cheguei a conseguir modificar a chave email da chave pai PRODUCTION efetuando chamada

searchKeyJson($jsonArray, , , "VALOR MODIFICADO")

Sendo que o parâmetro $keyUpdate é facultativo, já que posso precisar alterar um chave que esta em primeiro nível no json.

 

Funcionou a modificação para este exemplo de json/array, mais não funciona em todos os casos, pois como monto as chaves na $jsonArray[$keyFather][$keyUpdate] = $valueUpdate; ele se perde quando não encontra as referencias conforme a profundidade do array.


A ideia inicial era que eu pudesse passar apenas a chave em si que quero manipular (exemplo: email), mais não funcionaria porque como no json do exemplo, eu tenho duas chaves e-mail, uma no PRODUCTION e outra no HOMOLOGATION, logo ou vai modificar todas as chaves, ou vai modificar apenas a primeira que encontrar.

 

Ai estou pesquisando para ver como posso resolver este pepinão (hehehe), tendo em vista que estes campos jsonb do banco tem configuração do sistema importantes que precisam ser manipulados em um front, não da para ficar ajustando eles não mão sempre que precisar.

 

Exemplo da Função completa ate o momento, com 2 json e duas chamadas para testar, ambas funcionam, porem como mencionei, dependendo da profundidade da chave não consegue fazer a atualização.

 

<?php

// $json_string = '{"COMISSAO":{"EXTRA":0.5,"AGENTE":0.5,"ESCRITORIO":1},"PROPOSTA":{"API":[4,5,6,7,8,9,10,11],"JUROS":[1,2],"PRAZO":[1,2,3,4],"MAX_JUROS":2}}';

$json_string = '{"CREDITOR":{"TAX":{"TAC":0.7},"ENABLE":"true","PRODUCTION":{"email":"email@dominio.com","senha":"12457895","BASE_URL":"https://domino.com"},"HOMOLOGATION":{"email":"email@dominio.com","senha":"12457895","BASE_URL":"https://domino.com"},"TARGET_VALUES":5000000}}';

$json = json_decode($json_string, true);

function searchKeyJson(array &$jsonArray, string $keyFather, string $keyUpdate = null, $valueUpdate = null)
{
    foreach ($jsonArray as $key => $value) {
        if ($key == $keyFather && $keyUpdate == null) {
            $jsonArray[$key] =  $valueUpdate;
            return $jsonArray;
        }
        if ($key == $keyFather && $keyUpdate != null) {
            $jsonArray[$keyFather][$keyUpdate] =  $valueUpdate;
            return $jsonArray;
        }
        if (is_array($value)) {
            if (($result = searchKeyJson($jsonArray[$key], $keyFather, $keyUpdate, $valueUpdate)) !== false) {
                return $jsonArray;
            }
        }
    }
    return false;
}

// Chamada da função sem passar a chave de resultado
$returnWithoutKey = searchKeyJson($json, "PRODUCTION", "email", "TESTE UPDATE");
// $returnWithoutKey = searchKeyJson($json, "PROPOSTA", "API", [1,2,5,8]);

// Exemplo de valor a ser modificado dentro do JSON
echo "RETORNO".PHP_EOL;
echo "========================".PHP_EOL;
print_r($returnWithoutKey);


?>

 

Editado por kania
Adição de conteúdo necessário

Compartilhar este post


Link para o post
Compartilhar em outros sites

Certo, acho que agora entendi melhor, mas infelizmente acredito que essa necessidade seja oriunda de um modelo de dados que poderia ser otimizado no futuro.

 

Dito isso, elaborei uma pequena solução que espero que lhe ajude. Veja:

 

1 - Primeiro pegamos o caminho até o subarray desejado:

function getSubArrayPath( $array, $wantedKey ) {
    $mode = RecursiveIteratorIterator::SELF_FIRST;
    $iterator = new RecursiveIteratorIterator( new RecursiveArrayIterator( $array ), $mode );
    
    foreach ( $iterator as $key => $value )
        if ( $wantedKey === $key ) {
            $keys[] = $key;
            for ( $i = $iterator->getDepth()-1; $i >= 0 ; --$i ) {
                $keys[] = $iterator->getSubIterator( $i )->key();
            }
            
            return $keys;
        }
    
    return [];
}

Essa função, que faz uso das classes nativas RecursiveIteratorIterator e RecursiveArrayIterator tem a simples tarefa de receber um array (como o que o json_decode nos entregou) e dizer qual o caminho deve ser feito para chegarmos até a chave desejada (no caso PRODUCTION).

Ela deve se comportar da seguinte maneira:

$path = getSubArrayPath( $array, $key );

Retorna isso:
Array
(
    [0] => PRODUCTION
    [1] => CREDITOR
)

 

 

2 - Agora que temos o caminho dinâmico que estava enraizado na nossa árvore de dados, podemos encontrar a referência do subarray que queremos manipular. Para isso criamos a seguinte função:

function &getSubArrayRef( &$array, $key ) {
    $path = getSubArrayPath( $array, $key );
    $ref =& $array;
    
    while ( $node = array_pop( $path ) )
        $ref =& $ref[ $node ];
    
    return $ref;
}

Essa função é simples: enviamos os dados e dizemos a chave que ela deve encontrar recursivamente. Ela vai internamente chamar a "getSubArrayPath()" para determinar o caminho e então retornar a referência do subarray que desejamos.

 

 

Se reunirmos tudo teríamos esse código:

<?php
$json = '{"CREDITOR":{"TAX":{"TAC":0.7},"ENABLE":"true","PRODUCTION":{"email":"email@dominio.com","senha":"12457895","BASE_URL":"https://domino.com"},"HOMOLOGATION":{"email":"email@dominio.com","senha":"12457895","BASE_URL":"https://domino.com"},"TARGET_VALUES":5000000}}';
$data = json_decode( $json, true );

function getSubArrayPath( $array, $wantedKey ) {
    $mode = RecursiveIteratorIterator::SELF_FIRST;
    $iterator = new RecursiveIteratorIterator( new RecursiveArrayIterator( $array ), $mode );
    
    foreach ( $iterator as $key => $value )
        if ( $wantedKey === $key ) {
            $keys[] = $key;
            for ( $i = $iterator->getDepth()-1; $i >= 0 ; --$i ) {
                $keys[] = $iterator->getSubIterator( $i )->key();
            }
            
            return $keys;
        }
    
    return [];
}

function &getSubArrayRef( &$array, $key ) {
    $path = getSubArrayPath( $array, $key );
    $ref =& $array;
    
    while ( $node = array_pop( $path ) )
        $ref =& $ref[ $node ];
    
    return $ref;
}

$ref = &getSubArrayRef( $data, 'PRODUCTION' );
$ref[ 'email' ] = 'novo e-mail';
$ref[ 'senha' ] = 'nova senha';
$ref[ 'atributo_novo' ] = 'AAAAAAAAAA';

print_r( $data );

 

E o retorno disso seria esse:

Array
(
    [CREDITOR] => Array
        (
            [TAX] => Array
                (
                    [TAC] => 0.7
                )

            [ENABLE] => true
            [PRODUCTION] => Array
                (
                    [email] => novo e-mail
                    [senha] => nova senha
                    [BASE_URL] => https://domino.com
                    [atributo_novo] => AAAAAAAAAA
                )

            [HOMOLOGATION] => Array
                (
                    [email] => email@dominio.com
                    [senha] => 12457895
                    [BASE_URL] => https://domino.com
                )

            [TARGET_VALUES] => 5000000
        )

)

 

Achei melhor dividir as responsabilidades e trazer as manipulações para fora da função, mas se você preferir fazer tudo numa chamada só, basta modificar o valor de $ref após o while da nossa função "getSubArrayRef()". Dessa forma você poderia enviar o valor desejado e já sair com o array modificado de dentro da função (como você estava fazendo antes).

Compartilhar este post


Link para o post
Compartilhar em outros sites

Olá Matheus, tudo bom?

Bacana sua ideia, funciona bem quando passamos bem definida as referencias, mais acaba no mesmo problema que eu ainda não consegui resolver, a função não fica genérica para tratar qualquer json passando um parent como referencia para alterar um children.

 

Por exemplo, vamos dizer que o mesmo json foi modificado em algum momento e agora preciso modificar a chave TAC, eu precisaria passar como paramento principal a chave TESTE ou CREDITOR, e nas referencias todas as chaves em cadeia para modificar o item correto, isto? Estou tentando facilitar a vida do Front em não ter que passar todas estas referencias para a API, mais não sei se tem como.

 

$ref = &getSubArrayRef( $data, 'TESTE' );
$ref[ 'TAX' ]['TAC'] = 'nova tac';

 

JSON MODIFICADO COM A CHAVE [TESTE]

{
  "TESTE": {
    "TAX": {
      "TAC": 0.7
    }
  },
  "CREDITOR": {
    "TAX": {
      "TAC": 0.7
    },
    "ENABLE": "true",
    "PRODUCTION": {
      "email": "TESTE 5 tregbg",
      "senha": "aL1VM8Z6qYsc",
      "BASE_URL": "https://api.bancarizaao.fiduciascm.digital/"
    },
    "HOMOLOGATION": {
      "email": "integracao@selectinvestimentos.com",
      "senha": "1234",
      "BASE_URL": "https://api.americanohoml.fiduciascm.digital/"
    },
    "TARGET_VALUES": 5000000
  }
}

 

Outra opção que estou vendo se seria viável é usar spred operator, ai o front continuaria tendo que passar as referencia de toda  a arvore que vai ter que ser modificada, contudo só passaria um lista de string, mais ainda não testei para ver se ficaria viável.

 

function teste(...$param){
    foreach($param as $key => $val){
        echo '["'.$val.'"]';
    }
}

return teste("CREDITOR", "PRODUCTION", "email");

 

Compartilhar este post


Link para o post
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisar ser um membro para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar Agora

  • Conteúdo Similar

    • Por violin101
      Caros amigos, saudações.
       
      Humildemente peço desculpa por postar uma dúvida que tenho.

      Preciso salvar no MySql, os seguinte Registro:

      1 - Principal
      ====> minha dúvida começa aqui
      ==========> como faço para o Sistema Contar Automaticamente o que estiver despois do 1.____?
      1.01 - Matriz
      1.01.0001 - Estoque
      1.01.0002 - Oficina
      etc

      2 - Secundário
      2.01 - Loja_1
      2.01.0001 - Caixa
      2.01.0002 - Recepção
      etc
       
      Resumindo seria como se fosse um Cadastro de PLANO de CONTAS CONTÁBEIL.

      Grato,


      Cesar









       
    • Por violin101
      Caros amigos, saudações.

      Por favor, me perdoa em recorrer a orientação dos amigos.

      Preciso fazer um Relatório onde o usuário pode Gerar uma Lista com prazo para vencimento de: 15 / 20/ 30 dias da data atual.

      Tem como montar uma SQL para o sistema fazer uma busca no MySql por período ou dias próximo ao vencimento ?

      Tentei fazer assim, mas o SQL me traz tudo:
      $query = "SELECT faturamento.*, DATE_ADD(faturamento.dataVencimento, INTERVAL 30 DAY), fornecedor.* FROM faturamento INNER JOIN fornecedor ON fornecedor.idfornecedor = faturamento.id_fornecedor WHERE faturamento.statusFatur = 1 ORDER BY faturamento.idFaturamento $ordenar ";  
      Grato,
       
      Cesar
       
       
       
       
    • Por violin101
      Caros amigos, saudações
       
      Por favor, me perdoa em recorrer a orientação dos amigos, tenho uma dúvida.
       
      Gostaria de uma rotina onde o Sistema possa acusar para o usuário antes dos 30 dias, grifar na Tabela o aviso de vencimento próximo, por exemplo:
       
      Data Atual: 15/11/2024
                                           Vencimento
      Fornecedor.....................Data.....................Valor
      Fornecedor_1...........01/12/2024..........R$ 120,00 <== grifar a linha de Laranja
      Fornecedor_1...........01/01/2025..........R$ 130,00
      Fornecedor_2...........15/12/2024..........R$ 200,00 <== grifar a linha de Amarelo
      Fornecedor_2...........15/01/2025..........R$ 230,00
      Fornecedor_3...........20/12/2024..........R$ 150,00
       
      Alguém tem alguma dica ou leitura sobre este assunto ?

      Grato,
       
      Cesar
    • Por violin101
      Caros amigos, saudações.

      Por favor, me perdoa em recorrer a ajuda dos amigos, mas preciso entender uma processo que não estou conseguindo sucesso.

      Como mencionado no Título estou escrevendo um Sistema Web para Gerenciamento de Empresa.
       
      Minha dúvida, que preciso muito entender:
      - preciso agora escrever a Rotina para Emissão de NFe e essa parte não estou conseguindo.
       
      tenho assistido alguns vídeos e leituras, mas não estou conseguindo sucesso, já fiz toda as importações das LIB da NFePhp conforme orientação.

      Preciso de ajuda.

      Algum dos amigos tem conhecimento de algum passo-a-passo explicando a criação dessa rotina ?

      tenho visto alguns vídeos com LARAVEL, mas quando tento utilizar e converter para PHP+Codeiginter, dá uma fila de erros que não entendo, mesmo informando as lib necessárias.

      Alguns do amigo tem algum vídeo, leitura explicando essa parte ?

      Grato,

      Cesar.
    • Por violin101
      Caros amigos, saudações.

      Por favor, me perdoa em recorrer ao auxílio dos amigos, mas preciso entender e resolver um problema.
       
      Tenho uma Rotina que o usuário seleciona os produtos que deseja para requerer ao setor responsável.
       
      O usuário escolhe um produto qualquer e Clicla em um button para incluir a lista.

      O problema que estou enfrentando é que após escolher o produto e teclar ENTER o Sistema já salva no BD.
       
      Gostaria de criar uma Tecla de Atalho, para quando incluir/escolher o produto na lista, o usuário tecla como exemplo:
      ALT+A  para agregar a lista
      ALT+S para salvar a lista de itens desejados.

      Assim, quando teclar enter, o sistema não dispara o GRAVAR na Base de Dados.

      Grato,

      Cesar
       
       
       
×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.