How to change the JSON serialization format of PHP Datetime?

Asked

Viewed 465 times

4

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

$date = new DateTime();

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

Exit:

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

Test in Ideone

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

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

2 answers

4

I made a scheme similar to Anderson’s answer. But instead of implementing the JsonSerializable in the date class, I make for the entire collection.

Thus:

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?

Is that in certain cases you could not change the instance of DateTime for another class implementation. Thus, creating a class that changes the collection, rather than changing the instance of DateTime in itself, it is possible to convert all the values of the structure to the desired format.

In the case of PHP 7, it could be more useful still making 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, it is possible to make an implementation where the JSON.stringify receives a callback in the second parameter (replacer) that allows you to scroll through each item of the object passed, and can return a different value according to a condition.

Come to think of it, I’ve done 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 of a structure and checks for defirmin which will be serialized for json according to callback return.

Thus:

$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"}"

3


Change the return of the class itself at runtime DateTime I believe it is not possible - even because it could generate side effects on the application.

A trivial solution would be to extend the class DateTime to a class of yours where you define the desired behavior. In this case, when the function json_encode receives an object that implements the interface JsonSerializable, it will return the value returned from the method jsonSerialize provided in the interface. Thus, 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 it is not possible to define another class - if you do not have control over the object instances, the solution is to use the method format right in the place of json_encode.

echo $d->format(DateTime::ISO8601) . PHP_EOL;

Browser other questions tagged

You are not signed in. Login or sign up in order to post.