How to change the PHP DateTime JSON serialization format?

4

In PHP, when I use a json_encode in an object of type DateTime , it has the following result:

$date = new DateTime();

echo json_encode(compact('date'), JSON_PRETTY_PRINT);

Output:

{
    "date": {
        "date": "2018-08-09 16:46:19.241129",
        "timezone_type": 3,
        "timezone": "UTC"
    }
}

Test at Ideone

This format for me is completely undesirable. I wanted it to come in DateTime::ISO8601 format.

Is there any way to customize the DateTime of PHP serialization?

    
asked by anonymous 09.08.2018 / 19:01

2 answers

3

Changing the return of the class itself DateTime at runtime I think it is not possible - because this could cause side effects in the application.

A trivial solution would be to extend the class DateTime to a class of your where you define the desired behavior. In this case, when the json_encode function receives an object that implements the JsonSerializable interface, it will return the value returned from the jsonSerialize method provided in the interface. So it would be possible to do something like:

class DateTimeISO8601 extends DateTime implements JsonSerializable
{
    public function jsonSerialize()
    {
        return $this->format(static::ISO8601);
    }
}

And then:

$d = new DateTimeISO8601('now', new DateTimeZone('America/Sao_Paulo'));

echo json_encode($d) . PHP_EOL;

Generating output in expected format: "2018-08-09T14:22:43-0300" .

If you can not define another class - if you do not have control over the instances of the object, the workaround is to use the format method even in json_encode .

echo $d->format(DateTime::ISO8601) . PHP_EOL;
    
09.08.2018 / 19:23
4

I did a scheme similar to Anderson's answer. But instead of implementing JsonSerializable in the date class, I do it for the entire collection.

So:

class CustomJsonSerialize implements JsonSerializable
{
    protected $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function jsonSerialize()
    {
        $data_to_serialize = $this->data;

        array_walk_recursive($data_to_serialize, function (&$value) {

            if ($value instanceof \DateTime) {
                $value = $value->format(DateTime::ISO8601);
            }
        });

        return $data_to_serialize;
    }
}


$data = [
    'nome'          => 'Wallace',
    'data_cadastro' => new DateTime,
    'idade'         => 28,
    'profissoes'   => [
        [
            'nome' => 'Programador',
            'data_inicio' => new DateTime('-5 years')
        ]
    ]
];



 echo json_encode(new CustomJsonSerialize($data));

The answer is:

{
    "nome": "Wallace",
    "data_cadastro": "2018-08-09T14:56:38-0300",    
    "idade": 28,
    "profissoes": [
        {
            "nome": "Programador",
            "data_inicio": "2013-08-09T14:56:38-0300"
        }
    ]
}

What is the importance of this?

In certain cases, you could not change the instance of DateTime to another class implementation. So by creating a class that changes the collection, instead of changing the instance of DateTime itself, you can convert all the values of the structure to the desired format.

In the case of PHP 7, it could be even more useful to make an anonymous class!

$serialized_data = json_encode(new class ($data) implements JsonSerializable {

    protected $data;

    protected function __construct(array $data)
    {
        $this->data = $data;
    }

    public function jsonSerialize()
    {
        $data_to_serialize = $this->data;

        array_walk_recursive($data_to_serialize, function (&$value) {

            if ($value instanceof \DateTime) {
                $value = $value->format(DateTime::ISO8601);
            }
        });

        return $data_to_serialize;
    }
});

Update: Imitating Javascript!

With Javascript, you can do an implementation where JSON.stringify receives a callback in the second parameter ( replacer ) that allows you to scroll through each item of the passed object, and can return a different value according to a condition. p>

Thinking about it, I did something similar to PHP .

Source code:

function json_encode_callback($data, callable $callback, $options = 0)
{
    $isIterable = static function ($data) {
        return is_array($data) || $data instanceof \stdClass || $data instanceof Iterator;
    };

    $recursiveCallbackApply = static function ($data, callable $callback) use(&$recursiveCallbackApply, $isIterable) {
        if (! $isIterable($data))
        {
            return $callback($data);
        }

        foreach ($data as $key => &$value) {
            if ($isIterable($value)) {
                $value = $recursiveCallbackApply($value, $callback);
                continue;
            }
            $value = $callback($value, $key);
        }
        return $data;
    };
    return json_encode($recursiveCallbackApply($data, $callback), $options);
}

The above function basically traverses each item in a structure and checks to determine what will be serialized to json according to callback return.

So:

$obj = new stdClass;

$obj->date = new DateTime;

$obj->name = "Wallace";

$result = json_encode_callback($obj, function ($value) {
    return $value instanceof \DateTime ? $value->format('c') : $value;
});

var_dump($result);

The result is:

 string(53) "{"date":"2018-08-09T17:41:27-03:00","name":"Wallace"}"
    
09.08.2018 / 20:01