Ir para conteúdo

POWERED BY:

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.
       
      Por favor, me permita tirar uma dúvida com os amigos.

      Tenho um Formulário onde o Usuário digita todos os Dados necessários.

      Minha dúvida:
      --> como faço após o usuário digitar os dados e salvar, o Sistema chamar uma Modal ou mensagem perguntando se deseja imprimir agora ?

      Grato,
       
      Cesar
    • Por Carcleo
      Tenho uma abela de usuarios e uma tabela de administradores e clientes.
      Gostaria de uma ajuda para implementar um cadastro
       
      users -> name, login, passord (pronta) admins -> user_id, registratiom, etc.. client -> user_id, registratiom, etc...
      Queria ajuda para extender de user as classes Admin e Client
      Olhem como estáAdmin
      <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Admin extends User {     use HasFactory;            protected $fillable = [         'name',         'email',         'password',         'registration'     ];      private string $registration;     public function create(         string $name,          string $email,          string $password,         string $registration     )     {         //parent::create(['name'=>$name, 'email'=>$email, 'password'=>$password]);         parent::$name = $name;         parent::$email = $email;         parent::$password = $password;         $this->registration = $registration;     } } User
      <?php namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class User extends Authenticatable {     /** @use HasFactory<\Database\Factories\UserFactory> */     use HasFactory, Notifiable;     static string $name;     static string $email;     static string $password;     /**      * The attributes that are mass assignable.      *      * @var list<string>      */     protected $fillable = [         'name',         'email',         'password',     ];          /**      * The attributes that should be hidden for serialization.      *      * @var list<string>      */     protected $hidden = [         'remember_token',     ];     /**      * Get the attributes that should be cast.      *      * @return array<string, string>      */     protected function casts(): array     {         return [             'email_verified_at' => 'datetime',             'password' => 'hashed',         ];     }          public function roles() : BelongsToMany {         return $this->belongsToMany(Role::class);     }       public function hasHole(Array $roleName): bool     {                 foreach ($this->roles as $role) {             if ($role->name === $roleName) {                 return true;             }         }         return false;     }         public function hasHoles(Array $rolesName): bool     {                 foreach ($this->roles as $role) {             foreach ($rolesName as $rolee) {             if ($role->name === $rolee) {                 return true;             }          }         }         return false;     }         public function hasAbility(string $ability): bool     {         foreach ($this->roles as $role) {             if ($role->abilities->contains('name', $ability)) {                 return true;             }         }         return false;     }     } Como gravar um Admin na tabela admins sendo que ele é um User por extensão?
      Tentei assim mas é claro que está errado...
      public function store(Request $request, Admin $adminModel) {         $dados = $request->validate([             "name" => "required",             "email" => "required|email",             "password" => "required",             "registration" => "required"         ]);         $dados["password"] =  Hash::make($dados["password"]);                  $admin = Admin::where("registration",  $dados["registration"])->first();                  if ($admin)              return                    redirect()->route("admin.new")                             ->withErrors([                                 'fail' => 'Administrador já cadastrados<br>, favor verificar!'                   ]);                            $newAdmin = $adminModel->create(                                    $dados['name'],                                    $dados['email'],                                    $dados['password'],                                    $dados['registration']                                 );         dd($newAdmin);         $adminModel->save();         //$adminModel::create($admin);                  return redirect()->route("admin.new")->with("success",'Cadastrado com sucesso');     }  
    • Por violin101
      Caros amigos, saudações.
       
      Gostaria de tirar uma dúvida com os amigos, referente a PDV.
       
      Estou escrevendo um Sistema com Ponto de Vendas, a minha dúvida é o seguinte, referente ao procedimento mais correto.

      Conforme o caixa vai efetuando a venda, o Sistema de PDV já realiza:
      a baixa direto dos produtos no estoque
      ou
      somente após concretizar a venda o sistema baixa os produtos do estoque ?
       
      Grato,
       
      Cesar
       
    • Por violin101
      Caros amigos do grupo, saudações e um feliz 2025.
       
      Estou com uma pequena dúvida referente a Teclas de Atalho.

      Quando o Caps Lock está ativado o Comando da Tecla de Atalho não funciona.
      ou seja:
      se estiver para letra minúscula ====> funciona
      se estiver para letra maiúscula ====> não funciona
       
      Como consigo evitar essa falha, tanto para Letra Maiúscula quanto Minúscula ?

      o Código está assim:
      document.addEventListener( 'keydown', evt => { if (!evt.ctrlKey || evt.key !== 'r' ) return;// Não é Ctrl+r, portanto interrompemos o script evt.preventDefault(); });  
      Grato,
       
      Cesar
    • Por violin101
      Caros amigos, saudações.
       
      Por favor, poderiam me ajudar.

      Estou com a seguinte dúvida:
      --> como faço para para implementar o input código do produto, para quando o usuário digitar o ID o sistema espera de 1s a 2s, sem ter que pressionar a tecla ENTER.

      exemplo:
      código   ----   descrição
           1       -----   produto_A
       
      Grato,
       
      Cesar
×

Informação importante

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