final public function exportObjectDataCopy(
        $objectRepresentation = 'array')
    {
        if (!in_array($objectRepresentation, ['stdClass', 'array'], true)) {
            throw new LazyJsonMapperException(sprintf(
                'Invalid object representation type "%s". Must be either "stdClass" or "array".',
                $objectRepresentation
            ));
        }

        // Make a shallow copy (copy-on-write) so that we will avoid affecting
        // our actual internal object data during the parsing below. Otherwise
        // we would turn all of OUR internal, still-unconverted sub-object
        // arrays into real objects on our REAL instance, and permanently cause
        // our object instance's memory usage to go up after an export call.
        $copy = clone $this;

        // Perform a NON-RECURSIVE analysis of the copy's top-level (own)
        // properties. This will CONVERT all of their values to the proper type,
        // and perform further sub-object creation (convert sub-object arrays
        // into real objects), etc. It also ensures no illegal values exist.
        // NOTE: For performance, we DON'T want recursive analysis, since we'll
        // soon be asking any sub-objects to analyze themselves one-by-one too!
        $analysis = $copy->exportClassAnalysis(false); // Never throws.

        // Abort if there are any BAD definitions (conversion failure due to
        // mismatches between defined class map and the actual data).
        if (!empty($analysis->bad_definitions)) { // Ignore missing_definitions
            $problemSummaries = $analysis->generateNiceSummaries();

            throw new LazyJsonMapperException(sprintf(
                'Unable to convert data to %s: %s',
                $objectRepresentation,
                $problemSummaries['bad_definitions'] // Tells them exact error.
            ));
        }

        // Now recursively process every other sub-object within the copy's data
        // via exportObjectDataCopy(), thus forcing all nested sub-objects
        // (which are still DIRECT REFERENCES to OUR "$this" original versions
        // of the objects, since objects are always by reference) to copy and
        // validate/convert THEMSELVES and their data and then return it
        // within the type of container we need. For a perfect final result.
        // NOTE: We iterate by &$value reference, but that WON'T affect the
        // objects they point to. It's a reference to the "_objectData" entry.
        array_walk_recursive($copy->_objectData, function (&$value, $key) use ($objectRepresentation) {
            // Only process objects. Everything else is already perfect (either
            // converted to the target-type if mapped or is valid "mixed" data).
            if (is_object($value)) {
                // Verify that the object is an instance of LazyJsonMapper.
                // NOTE: It SHOULDN'T be able to be anything else, since the
                // object analysis above has already verified that any "mixed"
                // (non-LazyJsonMapper) properties only contain basic PHP types.
                // But this check is a cheap safeguard against FUTURE bugs.
                if (!$value instanceof self) {
                    throw new LazyJsonMapperException(sprintf(
                        'Unable to convert data to %s: Unexpected "%s" object in property/key "%s", but we expected an instance of a LazyJsonMapper object.',
                        $objectRepresentation,
                        Utilities::createStrictClassPath(get_class($value)),
                        $key
                    ));
                }

                // Now just ask the sub-object to give us a copy of ITS verified
                // + converted data wrapped in the desired container type.
                // NOTE: This will be resolved recursively as-necessary for all
                // objects in the entire data tree.
                $value = $value->exportObjectDataCopy($objectRepresentation); // Throws.
            }
        });

        // Convert the outer data-holder to the object-representation type.
        if ($objectRepresentation === 'stdClass') {
            // When they want a stdClass, we MUST create it ourselves and assign
            // all of the internal data key-value pairs on it, with string-keys.
            $outputContainer = new stdClass();
            foreach ($copy->_objectData as $k => $v) {
                $outputContainer->{(string) $k} = $v;
            }
        } else { // 'array'
            // For an array-representation, we'll simply steal the copy's array.
            $outputContainer = $copy->_objectData;
        }

        // Voila, we now have their desired container, with cleaned-up and fully
        // validated copies of all of our internal data. And we haven't affected
        // any of our real $this data at all. Clones rock!
        // NOTE: And since $copy goes out of scope now, there will be no other
        // references to any of the data inside the container. It is therefore
        // fully encapsulated, standalone data which is safe to manipulate.
        return $outputContainer;
    }
final public function exportObjectDataCopy(
        $objectRepresentation = 'array')
    {
        if (!in_array($objectRepresentation, ['stdClass', 'array'], true)) {
            throw new LazyJsonMapperException(sprintf(
                'Invalid object representation type "%s". Must be either "stdClass" or "array".',
                $objectRepresentation
            ));
        }

        // Make a shallow copy (copy-on-write) so that we will avoid affecting
        // our actual internal object data during the parsing below. Otherwise
        // we would turn all of OUR internal, still-unconverted sub-object
        // arrays into real objects on our REAL instance, and permanently cause
        // our object instance's memory usage to go up after an export call.
        $copy = clone $this;

        // Perform a NON-RECURSIVE analysis of the copy's top-level (own)
        // properties. This will CONVERT all of their values to the proper type,
        // and perform further sub-object creation (convert sub-object arrays
        // into real objects), etc. It also ensures no illegal values exist.
        // NOTE: For performance, we DON'T want recursive analysis, since we'll
        // soon be asking any sub-objects to analyze themselves one-by-one too!
        $analysis = $copy->exportClassAnalysis(false); // Never throws.

        // Abort if there are any BAD definitions (conversion failure due to
        // mismatches between defined class map and the actual data).
        if (!empty($analysis->bad_definitions)) { // Ignore missing_definitions
            $problemSummaries = $analysis->generateNiceSummaries();

            throw new LazyJsonMapperException(sprintf(
                'Unable to convert data to %s: %s',
                $objectRepresentation,
                $problemSummaries['bad_definitions'] // Tells them exact error.
            ));
        }

        // Now recursively process every other sub-object within the copy's data
        // via exportObjectDataCopy(), thus forcing all nested sub-objects
        // (which are still DIRECT REFERENCES to OUR "$this" original versions
        // of the objects, since objects are always by reference) to copy and
        // validate/convert THEMSELVES and their data and then return it
        // within the type of container we need. For a perfect final result.
        // NOTE: We iterate by &$value reference, but that WON'T affect the
        // objects they point to. It's a reference to the "_objectData" entry.
        array_walk_recursive($copy->_objectData, function (&$value, $key) use ($objectRepresentation) {
            // Only process objects. Everything else is already perfect (either
            // converted to the target-type if mapped or is valid "mixed" data).
            if (is_object($value)) {
                // Verify that the object is an instance of LazyJsonMapper.
                // NOTE: It SHOULDN'T be able to be anything else, since the
                // object analysis above has already verified that any "mixed"
                // (non-LazyJsonMapper) properties only contain basic PHP types.
                // But this check is a cheap safeguard against FUTURE bugs.
                if (!$value instanceof self) {
                    throw new LazyJsonMapperException(sprintf(
                        'Unable to convert data to %s: Unexpected "%s" object in property/key "%s", but we expected an instance of a LazyJsonMapper object.',
                        $objectRepresentation,
                        Utilities::createStrictClassPath(get_class($value)),
                        $key
                    ));
                }

                // Now just ask the sub-object to give us a copy of ITS verified
                // + converted data wrapped in the desired container type.
                // NOTE: This will be resolved recursively as-necessary for all
                // objects in the entire data tree.
                $value = $value->exportObjectDataCopy($objectRepresentation); // Throws.
            }
        });

        // Convert the outer data-holder to the object-representation type.
        if ($objectRepresentation === 'stdClass') {
            // When they want a stdClass, we MUST create it ourselves and assign
            // all of the internal data key-value pairs on it, with string-keys.
            $outputContainer = new stdClass();
            foreach ($copy->_objectData as $k => $v) {
                $outputContainer->{(string) $k} = $v;
            }
        } else { // 'array'
            // For an array-representation, we'll simply steal the copy's array.
            $outputContainer = $copy->_objectData;
        }

        // Voila, we now have their desired container, with cleaned-up and fully
        // validated copies of all of our internal data. And we haven't affected
        // any of our real $this data at all. Clones rock!
        // NOTE: And since $copy goes out of scope now, there will be no other
        // references to any of the data inside the container. It is therefore
        // fully encapsulated, standalone data which is safe to manipulate.
        return $outputContainer;
    }
final public function exportObjectDataCopy ( $ objectRepresentation = 'array' ) { if ( ! in_array ( $ objectRepresentation , [ 'stdClass' , 'array' ] , true ) ) { throw new LazyJsonMapperException ( sprintf ( 'Invalid object representation type "%s". Must be either "stdClass" or "array".' , $ objectRepresentation ) ) ; } // Make a shallow copy (copy-on-write) so that we will avoid affecting // our actual internal object data during the parsing below. Otherwise // we would turn all of OUR internal, still-unconverted sub-object // arrays into real objects on our REAL instance, and permanently cause // our object instance's memory usage to go up after an export call. $ copy = clone $ this ; // Perform a NON-RECURSIVE analysis of the copy's top-level (own) // properties. This will CONVERT all of their values to the proper type, // and perform further sub-object creation (convert sub-object arrays // into real objects), etc. It also ensures no illegal values exist. // NOTE: For performance, we DON'T want recursive analysis, since we'll // soon be asking any sub-objects to analyze themselves one-by-one too! $ analysis = $ copy -> exportClassAnalysis ( false ) ; // Never throws. // Abort if there are any BAD definitions (conversion failure due to // mismatches between defined class map and the actual data). if ( ! empty ( $ analysis -> bad_definitions ) ) { // Ignore missing_definitions $ problemSummaries = $ analysis -> generateNiceSummaries ( ) ; throw new LazyJsonMapperException ( sprintf ( 'Unable to convert data to %s: %s' , $ objectRepresentation , $ problemSummaries [ 'bad_definitions' ] // Tells them exact error. ) ) ; } // Now recursively process every other sub-object within the copy's data // via exportObjectDataCopy(), thus forcing all nested sub-objects // (which are still DIRECT REFERENCES to OUR "$this" original versions // of the objects, since objects are always by reference) to copy and // validate/convert THEMSELVES and their data and then return it // within the type of container we need. For a perfect final result. // NOTE: We iterate by &$value reference, but that WON'T affect the // objects they point to. It's a reference to the "_objectData" entry. array_walk_recursive ( $ copy -> _objectData , function ( & $ value , $ key ) use ( $ objectRepresentation ) { // Only process objects. Everything else is already perfect (either // converted to the target-type if mapped or is valid "mixed" data). if ( is_object ( $ value ) ) { // Verify that the object is an instance of LazyJsonMapper. // NOTE: It SHOULDN'T be able to be anything else, since the // object analysis above has already verified that any "mixed" // (non-LazyJsonMapper) properties only contain basic PHP types. // But this check is a cheap safeguard against FUTURE bugs. if ( ! $ value instanceof self ) { throw new LazyJsonMapperException ( sprintf ( 'Unable to convert data to %s: Unexpected "%s" object in property/key "%s", but we expected an instance of a LazyJsonMapper object.' , $ objectRepresentation , Utilities :: createStrictClassPath ( get_class ( $ value ) ) , $ key ) ) ; } // Now just ask the sub-object to give us a copy of ITS verified // + converted data wrapped in the desired container type. // NOTE: This will be resolved recursively as-necessary for all // objects in the entire data tree. $ value = $ value -> exportObjectDataCopy ( $ objectRepresentation ) ; // Throws. } } ) ; // Convert the outer data-holder to the object-representation type. if ( $ objectRepresentation === 'stdClass' ) { // When they want a stdClass, we MUST create it ourselves and assign // all of the internal data key-value pairs on it, with string-keys. $ outputContainer = new stdClass ( ) ; foreach ( $ copy -> _objectData as $ k => $ v ) { $ outputContainer -> { ( string ) $ k } = $ v ; } } else { // 'array' // For an array-representation, we'll simply steal the copy's array. $ outputContainer = $ copy -> _objectData ; } // Voila, we now have their desired container, with cleaned-up and fully // validated copies of all of our internal data. And we haven't affected // any of our real $this data at all. Clones rock! // NOTE: And since $copy goes out of scope now, there will be no other // references to any of the data inside the container. It is therefore // fully encapsulated, standalone data which is safe to manipulate. return $ outputContainer ; }
Get a processed copy of this object instance's internal data contents.

It is recommended that you save the result if you intend to re-use it
multiple times, since each call to this function will need to perform
copying and data conversion from our internal object representation.

Note that the conversion process will recursively validate and convert
all properties in the entire internal data hierarchy. You can trust that
the returned result will be perfectly accurate and follow your class map
property type-rules. This does however mean that the data may not be the
same as the input array you gave to `__construct()`. For example, if your
input contained an array `["1", "2"]` and your type definition map says
that you want `int` for that property, then you'd get `[1, 2]` as output.

The conversion is always done on a temporary COPY of the internal data,
which means that you're welcome to run this function as much as you want
without causing your object to grow from resolving all its sub-objects.

It also means the returned copy belongs to YOU, and that you're able to
do ANYTHING with it, without risk of affecting any of OUR internal data.

`WARNING:` If you intend to use the result to `json_encode()` a new JSON
object then please DON'T do that. Look at `asJson()` instead, which gives
you full control over all JSON output parameters and properly handles the
conversion in all scenarios, without you needing to do any manual work.

Also look at the separate `asStdClass()` and `asArray()` functions, for
handy shortcuts instead of manually having to call this longer function.

@param string $objectRepresentation What container to use to represent
`LazyJsonMapper` objects. Can be
either `array` (useful if you require
that the result is compatible with
`__construct()`), or `stdClass` (the
best choice if you want to be able to
identify subobjects via
`is_object()`).

@throws LazyJsonMapperException If there are any conversion problems.

@return stdClass|array A processed copy of the internal data, using the
desired container type to represent all objects.

@see LazyJsonMapper::asStdClass()
@see LazyJsonMapper::asArray()
@see LazyJsonMapper::asJson()
@see LazyJsonMapper::printJson()
Get a processed copy of this object instance s internal data contents .