2 json сохранить измененные и отсутствующие данные в новый json файл

Содержимое test.json

{
  "data": {
    "accountid": [{
      "series": {
        "smart1": 5,
        "smart2": 17,
        "smart3": 0,
        "smart4": 1
      }
    }, {
      "series": {
        "smart1": 5,
        "smart2": 25,
        "smart3": 5,
        "smart4": 1
      }
    }]
  }
}

Содержимое test1.json

{
  "data": {
    "accountid": [{
      "series": {
        "smart1": 5,
        "smart2": 22,
        "smart3": 0,
        "smart4": 45
      }
    }, {
      "series": {
        "smart1": 8,
        "smart2": 49,
        "smart3": 5,
        "smart4": 1,
        "smart5": 43
      }
    }]
  }
}

Скрипт, который проверяет 2 файла json и если находит изменение, выводит на экран.

$json1 = file_get_contents('test.json');
$json2 = file_get_contents('test1.json');
$a = json_decode($json1, true);
$b = json_decode($json2, true);


function doublejson($a, $b, $strkey)
   {
     foreach ($a as $key => $value)
        {
            foreach ($b as $key2 => $value2)
            {
                if($key==$key2)
                  {
                    if(gettype($value) != "array")
                     {
                        if($value2!=$value)
                        {

                            $c.="<br>".$strkey.'"'.$key.'"'.":".'"'.$value.'"'." => ".$strkey.'"'.$key2.'"'.":".'"'.$value2.'"'."<br>";
                            break;
                        }
                         else
                          {
                            break;
                          }
                     }
                             else
                                 {
                                  $c.=doublejson($a[$key], $b[$key], $strkey.'"'.$key.'":');
                                  break;
                                 }
                  }
            }   

        }
    return $c;
    }  


echo "<pre>".doublejson($a, $b, "")."</pre>";

Выводит следующее: только изменившиеся значения

"data":"accountid":"0":"series":"smart2":"17" => "data":"accountid":"0":"series":"smart2":"22"

"data":"accountid":"0":"series":"smart4":"1" => "data":"accountid":"0":"series":"smart4":"45"

"data":"accountid":"1":"series":"smart1":"5" => "data":"accountid":"1":"series":"smart1":"8"

"data":"accountid":"1":"series":"smart2":"25" => "data":"accountid":"1":"series":"smart2":"49"

Вопрос1: Как мне сохранить только изменившиеся данные с учетом вычитания value2-value1, должен сохранить в новый файл json такую структуру:

{
  "data": {
    "accountid": [{
      "series": {
        "smart2": 5, // 22-17
        "smart4": 44  // 45-1
      }
    }, {
      "series": {
        "smart1": 3, // 8-5
        "smart2": 24 // 49-25
      }
    }]
  }
}

Вопрос2: Какой код нужно дописать, чтобы сохранялось ключ-значение из второго json, которого нет в первом, должно получиться так:

{
      "data": {
        "accountid": [{
          "series": {
            "smart2": 5, // 22-17
            "smart4": 44  // 45-1
          }
        }, {
          "series": {
            "smart1": 3, // 8-5
            "smart2": 24, // 49-25
            "smart5": 43 // этот ключ-значение отсутствует в первом json и должен сохраниться в новом файле json
          }
        }]
      }
    }

Ответы (2 шт):

Автор решения: teran

Допустим ключи второго массива соответствуют первому, плюс могут быть новые.

  1. Для удобства извлечем данные, которые непосредственно надо сравнивать (то есть сами массивы сравнения).
  2. Берем попарно два таких массива $a, $b.
  3. Идем по массиву $b.
  4. Если ключ в массиве $a отсутствует, то пропускаем.
  5. Если есть, то сравниваем значения. Если изменилось, вычитаем
  6. Если не изменилось, удаляем из массива.
  7. возвращаем результат с ключом series

-

function diff($a, $b){
    $a = array_column($a['data']['accountid'], 'series');   // 1
    $b = array_column($b['data']['accountid'], 'series');

    $result = array_map(function($a, $b){                   // 2 
            foreach($b as $k => &$v){                       // 3
                if(!isset($a[$k]))  continue;               // 4 
                if($a[$k] != $v) $v -= $a[$k];              // 5 
                else unset($b[$k]);                         // 6    
            }
            return ['series' => $b];                        // 7  
          }, $a, $b);

    return ['data' => ['accountid' => $result]];
}
→ Ссылка
Автор решения: AlexBrin

Понимаю, что это не совсем относится к задаче, но это нужно переписать, чтобы получить более аккуратный и читаемый код

Возможно я просто не совсем понимаю задачу, но это можно сделать одним циклом с рекурсией. Для задачи нам нужно взять взять массив, на который мы будем опираться. В нашем случае это второй ($two) В начале каждой итерации необходимо проверить существование элемента в первом массиве: isset($one[$key]) Далее получить значение и проверить его тип. Вместо gettype($val) == 'array' можно использовать функцию is_array, которая вернет bool. То что нам сейчас нужно. Так как сама функиция ожидает на вход два массива, мы и будем проверять сразу два значения, являются ли они массивами. Это нужно, чтобы избежать неожиданных последствий в виде внезапного Warning

Это решение делает то же самое, что и решение @teran, но является более универсальным (хотя можно использовать только его функцию array_map с аргументами и получится почти то же самое):

function findDoubleAndUnique($one, $two) {
    $double = [];

    foreach($two as $key => $val) {
        if(!isset($one[$key])) {
            $double[$key] = $val;
            continue;
        }

        $val2 = $one[$key];

        if(is_array($val) && is_array($val2)) {
            $double[$key] = findDoubleAndUnique($val2, $val);
        } else if ($val != $val2) {
            $double[$key] = $val - $val2;
        }
    }

    return $double;
}

Ну и использование:

$json1 = json_decode(file_get_contents('1.json'), true);
$json2 = json_decode(file_get_contents('2.json'), true);

file_put_contents('out.json', json_encode(findDoubleAndUnique($json1, $json2), JSON_PRETTY_PRINT));
→ Ссылка