Как игнорировать свойство null с помощью JsonSerializable:: jsonSerialize()?

Скажем, у нас есть простой объект для сериализации с вложенным объектом:

class User implements \JsonSerializable
{

    private $name;
    private $email;
    private $address;

    public function jsonSerialize()
    {
        return [
            'name'  => $this->name,
            'email'  => $this->email,
            'address' => $this->address
        ];
    }
}

Вложенный объект:

class Address implements \JsonSerializable
{

    private $city;
    private $state;

    public function jsonSerialize()
    {
        return [
            'city'  => $this->city,
            'state' => $this->state
        ];
    }
}

Мы используем json_encode() для сериализации, это будет использовать изначально JsonSerializable:: jsonSerialize():

$json = json_encode($user);

Если $name и $state равны нулю, как это сделать:

{
    "email": "[email protected]",
    {
        "city": "Paris"
    }
}

вместо этого:

{
    "name": null,
    "email": "[email protected]",
    {
        "city": "Paris",
        "state": null
    }
}

Ответ 1

wrap array_filter вокруг возвращаемых массивов, например

public function jsonSerialize()
    return array_filter([
        'city'  => $this->city,
        'state' => $this->state
    ]);
}

Это приведет к тому, что любые записи будут равны false (слабо сравниваются), включая любые нулевые значения, а также 0 и false. Если вам нужно строгое, например, только нули, введите следующий обратный вызов:

function($val) { return !is_null($val); }

Смотрите документацию для array_filter:

(PHP 4 >= 4.0.6, PHP 5, PHP 7)

Итерирует по каждому значению в массиве, передавая их функции обратного вызова. Если функция обратного вызова возвращает значение true, текущее значение из массива возвращается в массив результатов. Клавиши массива сохраняются.

Другим вариантом было бы использовать JMX Serializer, который является высоко настраиваемым сериализатором для JSON, XML и YAML. Это намного тяжелее. Подробнее см. Исключить свойства null в JMS Serializer.

Ответ 2

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

<?php

$a = (object) array(
  'first' => null,
  'second' => 10,
  'third' => (object) array(
    'second-first' => 100,
    'second-second' => null,
    'second-third' => 300
  ),
  'fourth' => 20,
  'fifth' => null,
  'sixth' => 10);

function remove_null_values($a) {
  $ret = (is_array($a) ? array() : new stdClass());
  foreach ($a as $k => $v) {
    if (is_array($v) || is_object($v)) {
      if (is_object($ret))
        $ret->$k = remove_null_values($v);
      else
        $ret[$k] = remove_null_values($v);
    }
    elseif (!is_null($v)) {
      if (is_object($ret))
        $ret->$k = $v;
      else
        $ret[$k] = $v;
    }
  }
  return $ret;
}

Вывод:

object(stdClass)#2 (6) {
  ["first"]=>
  NULL
  ["second"]=>
  int(10)
  ["third"]=>
  object(stdClass)#1 (3) {
    ["second-first"]=>
    int(100)
    ["second-second"]=>
    NULL
    ["second-third"]=>
    int(300)
  }
  ["fourth"]=>
  int(20)
  ["fifth"]=>
  NULL
  ["sixth"]=>
  int(10)
}
object(stdClass)#3 (4) {
  ["second"]=>
  int(10)
  ["third"]=>
  object(stdClass)#4 (2) {
    ["second-first"]=>
    int(100)
    ["second-third"]=>
    int(300)
  }
  ["fourth"]=>
  int(20)
  ["sixth"]=>
  int(10)
}

EDIT: я обновил код для работы как вложенных массивов, так и объектов, в случае, если вам не нужна функция части массивов, это тривиально, чтобы скомпенсировать это, я позволю вам обрабатывать детали