protected function sanitize($data, array &$processed = [])
    {
        if (\is_array($data)) {
            /**
             * @var array $data
             */
            foreach ($data as $name => $value) {
                $data[$name] = $this->sanitize($value, $processed);
            }

            return $data;
        }

        if (\is_object($data)) {
            /**
             * @var object $data
             */
            $class_name = \get_class($data);
            $hash = \spl_object_hash($data);

            if (isset($processed[$hash])) {
                // NOTE: duplicate objects (circular references) are omitted to prevent recursion.

                return [self::CLASS_NAME => $class_name];
            }

            $processed[$hash] = true;

            if ($data instanceof \JsonSerializable) {
                // NOTE: this doesn't serialize to JSON, it only marshalls to a JSON-compatible data-structure
                $data = $this->sanitize($data->jsonSerialize(), $processed);
            } elseif ($data instanceof \DateTimeInterface) {
                $data = $this->extractDateTimeProperties($data);
            } elseif ($data instanceof \Throwable) {
                $data = $this->extractExceptionProperties($data);
            } else {
                $data = $this->sanitize($this->extractObjectProperties($data), $processed);
            }

            return \array_merge([self::CLASS_NAME => $class_name], $data);
        }

        if (\is_scalar($data)) {
            return $data; // bool, int, float
        }

        if (\is_resource($data)) {
            $resource = \explode('#', (string) $data);

            return [
                self::CLASS_NAME => 'resource<' . \get_resource_type($data) . '>',
                'id' => \array_pop($resource),
            ];
        }

        return null; // omit any other unsupported types (e.g. resource handles)
    }