private function serializeObject($object, string $className = null)
    {
        if (is_null($className)) {
            $index = array_search($object, $this->references, true);

            if ($index !== false) {
                $this->referencesUsed[] = $index;

                return ['@ref' => $index];
            }

            $this->references[] = $object;
        }

        $data = ['@type' => get_class($object)];

        $reflectionClass = new \ReflectionClass($className ? $className : $object);

        // parent class
        $parent = $reflectionClass->getParentClass();
        if ($parent) {
            $parentData = $this->serializeObject($object, $parent->getName());
            if (count($parentData) > 0) {
                $data = array_merge($data, $parentData);
            }
        }

        // current $object
        foreach ($reflectionClass->getProperties() as $property) {
            $property->setAccessible(true);
            $value = $property->getValue($object);
            $name = $property->getName();

            // for parent class, don't reserialize public & protected properties
            if (isset($data[$name])) {
                continue;
            }

            // optimization: only save value if not the default value
            $defaults = $reflectionClass->getDefaultProperties();
            if (array_key_exists($name, $defaults) && $defaults[$name] === $value) {
                continue;
            }

            $this->serializeValue($name, $value, $data);
        }

        // ugly hack for public undeclared properties and
        // for internal object like datetime that can't be accessed by reflection
        // @see http://news.php.net/php.internals/93826%20view%20original
        if (!$className) {
            foreach (get_object_vars($object) as $key => $value) {
                if (!isset($data[$key])) {
                    $this->serializeValue($key, $value, $data);
                }
            }
        }

        return $data;
    }