final public function exportClassAnalysis(
        $recursiveScan = true)
    {
        $result = new ClassAnalysis();

        // All problems with OUR class will get filed under our class name.
        $definitionSource = get_class($this);

        // Ensure that all object-data properties exist in our class definition,
        // and that their values can be converted as described in the class map.
        foreach ($this->_objectData as $propName => $value) {
            // Check if property exists in the class map and get its definition.
            // NOTE: Normally, this function can throw, but not with the way we
            // are calling it, because we KNOW that the property exists in at
            // least the object data, so we DON'T have to catch any errors!
            $propDef = $this->_getPropertyDefinition($propName);

            // Regardless of whether it's defined or not, we MUST now "get" it
            // to validate/convert the property's contents, to ensure it's safe.
            try {
                // Trying to "get" the property forces complete validation of
                // the internal property data and non-recursively creates all
                // sub-objects (if any). In case of any errors, this call will
                // throw an exception with an error description.
                // NOTE: Calling this getter is EXTREMELY important even for
                // UNDEFINED (untyped) properties, because it ensures that no
                // illegal data values (such as Resources or non-LazyJsonMapper
                // objects) exist within the data even in undefined properties.
                // NOTE: In the case of arrays, it validates the whole
                // arrayDepth and ensures that the array is well-formed and
                // contains the exact type at the exact depth specified (but it
                // accepts NULL values anywhere in the chain of arrays, and it
                // accepts empty arrays at non-max depth). It also verifies
                // type-coercion to built-in PHP types. And in the case of
                // objects, it verifies that they are successfully constructed.
                $value = $this->_getProperty($propName); // Throws.

                // Recursively check all internal objects to make sure they also
                // have all properties from their own raw JSON data.
                // NOTE: Nothing in this sub-block throws any exceptions, since
                // everything was validated by _getProperty(). And the deeper
                // exportClassAnalysis() calls don't throw anything either.
                if ($recursiveScan && $value !== null && $propDef->isObjectType) {
                    // NOTE: We don't have to validate array-depth/type
                    // correctness, since _getProperty() already did that for
                    // us. Nothing will be isObjectType unless it's really a
                    // LazyJsonMapper class. Also, since the $value is not NULL
                    // (see above) and it's an "array of [type]" property, then
                    // we KNOW the $value is_array(), since it validated as OK.
                    if ($propDef->arrayDepth > 0) { // Array of objects.
                        array_walk_recursive($value, function (&$obj) use (&$result) {
                            if (is_object($obj)) { // Could be inner NULL too.
                                $result->mergeAnalysis($obj->exportClassAnalysis());
                            }
                        });
                    } else { // Non-"array of" object property.
                        $result->mergeAnalysis($value->exportClassAnalysis());
                    }
                }
            } catch (LazyJsonMapperException $e) {
                // Unable to get the value of this property... which usually
                // means that the property cannot be type-coerced as requested,
                // or that the JSON data doesn't match the definition (such as
                // having a deeper JSON array than what the definition says), or
                // that invalid data was encountered (such as encountering an
                // internal non-Lazy object/resource instead of the expected
                // data), or that the property's class could not be constructed
                // (which may really only happen due to a user's custom _init()
                // function failing, since our own classmap's compilation has
                // already taken care of successfully verifying the classmap
                // compilation of ALL classes referred to by our ENTIRE property
                // hierarchy). All of those are user-caused errors...

                // We'll save the exception details message as-is...
                // TODO: This also means that undefined properties containing
                // weird data (objects or resources) would throw above and get
                // logged as "bad definition" despite NOT having any definition.
                // We may want to add a check here for UndefinedProperty and
                // prepend something to the exception message in that case, to
                // tell the user that their UNDEFINED property contained the bad
                // data. But seriously, they can NEVER have such invalid data
                // inside of a real json_decode() input array, so nobody sane is
                // going to be warned of "bad definition" for their undefined
                // properties! And we can't just log it as "missing" since we
                // NEED all "bad data" to be logged as bad_definitions, since
                // that's what our various data validators look at to detect
                // SERIOUS data errors! Besides, their missing definition WILL
                // also get logged under "missing_definitions" in the next step.
                $result->addProblem(
                    $definitionSource,
                    'bad_definitions',
                    $e->getMessage()
                );
            }

            // Now check if we lacked a user-definition for this JSON property.
            if ($propDef instanceof UndefinedProperty) {
                // NOTE: We need the (string) casting in case of int-keys. Which
                // can happen if the user gives us a manually created, non-JSON
                // constructor array containing non-associative integer keys.
                $result->addProblem(
                    $definitionSource,
                    'missing_definitions',
                    (string) $propName
                );
            }
        }

        // Nicely sort the problems and remove any duplicates.
        $result->sortProblemLists();

        return $result;
    }