SQInteger JSON::encodeInstance()

in AzureSphereSquirrel/HLCore/json.cpp [743:951]


SQInteger JSON::encodeInstance(HSQUIRRELVM vm, HSQOBJECT object, SQChar *&jsonString, SQInteger &jsonStringStorageSize, SQInteger &jsonStringSize, SQInteger depth)
{
    // Push the instance to the stack and retrieve its typetag (that identifies if it's a special type)
    SQUserPointer typeTag;
    sq_pushobject(vm, object);
    sq_gettypetag(vm, -1, &typeTag);

    // Determine if the instance if of type 'blob', otherwise treat it generically
    if((SQUnsignedInteger)typeTag == 0x80000002)
    {
        // The instance was a blob, retrieve a reference to its inner data structure and size
        const SQChar* blobData;
        sqstd_getblob(vm, -1, (SQUserPointer*)&blobData);
        SQInteger blobDataSize = sqstd_getblobsize(vm, -1);

        // Pop the blob object from the stack
        sq_poptop(vm);

        // Escape and encode the blob
        if(SQ_FAILED(escapeAndEncode(vm, blobData, blobDataSize, jsonString, jsonStringStorageSize, jsonStringSize)))
        {
            return SQ_ERROR;
        }
    }
    else
    {
        // Determine if the instance has a '_serializeRaw' function (will pop the key and push result to the stack)
        sq_pushstringex(vm, "_serializeRaw", -1, SQTrue);
        if(SQ_SUCCEEDED(sq_get(vm, -2)) && sq_gettype(vm, -1) == OT_CLOSURE)
        {
            // Push the instance object to the stack as the 'this' parameter and call '_serializeRaw'
            sq_pushobject(vm, object);
            if(SQ_FAILED(sq_call(vm,1,true,true)))
            {
                return sq_throwerror(vm, "Unable to execute instance's _serializeRaw() function");
            }

            // Attempt to convert the returned value to a string, pushing the result to the stack
            if(SQ_FAILED(sq_tostring(vm, -1)))
            {
                return sq_throwerror(vm, "Instance's _serializeRaw did not produce a tostring(able) output");
            }

            // Retrieve the converted string and string size from the stack
            const SQChar* string;
            SQInteger stringSize;
            sq_getstringandsize(vm, -1, &string, &stringSize); // Ignore the return value, we know there's a string here

            // Grow storage to account for '"string"'
            growStorage(jsonString, jsonStringStorageSize, jsonStringSize+2+stringSize);

            jsonString[jsonStringSize++] = '"';
            memcpy(&jsonString[jsonStringSize], string, stringSize);
            jsonStringSize += stringSize;
            jsonString[jsonStringSize++] = '"';

            // Pop the instance object, function closure and converted string from the stack
            // (we're doing this here as the lifetime of the converted string is questionable)
            sq_pop(vm, 3);

            return SQ_OK;
        }

        // Determine if the instance has a '_serialize' function (will pop the key and push result to the stack)
        sq_pushstringex(vm, "_serialize", -1, SQTrue);
        if(SQ_SUCCEEDED(sq_get(vm, -2)) && sq_gettype(vm, -1) == OT_CLOSURE)
        {
            // Push the instance object to the stack as the 'this' parameter and call '_serialize'
            sq_pushobject(vm, object);
            if(SQ_FAILED(sq_call(vm,1,true,true)))
            {
                return sq_throwerror(vm, "Unable to execute instance's _serialize() function");
            }

            // Retrieve the returned object from the stack and recursively encode it
            HSQOBJECT returnedObject;
            sq_resetobject(&returnedObject);
            sq_getstackobj(vm, -1, &returnedObject);

            if(SQ_FAILED(encodeObject(vm, returnedObject, jsonString, jsonStringStorageSize, jsonStringSize, depth+1)))
            {
                return SQ_ERROR;
            }

            // Pop the instance object, function closure and returned object from the stack
            // (we're doing this here as the lifetime of the returned object is questionable)
            sq_pop(vm, 3);

            return SQ_OK;
        }

        // Determine if the instance is nexti iteratable by trying and seeing if an error was produced
        sq_reseterror(vm);

        // Grow storage to account for '{'
        growStorage(jsonString, jsonStringStorageSize, jsonStringSize+1);

        jsonString[jsonStringSize++] = '{';

        // Take a snapshot of the jsonStringSize to revert should nexti fail
        SQInteger outputSizeSnapshot = jsonStringSize;

        // Push a blank iterator to the stack
        sq_pushnull(vm);

        // Iterate over each entry within the instance via nexti (will push key and value onto the stack)
        while(SQ_SUCCEEDED(sq_next(vm, -2)))
        {
            // Retrieve the value
            HSQOBJECT value;
            sq_resetobject(&value);
            sq_getstackobj(vm, -1, &value);

            // Ignore the entry if it refers to a function (we're only serializing properties)
            if(!sq_isfunction(value))
            {
                // Attempt to convert the key to a string (nexti doesn't guarantee strings)
                if(SQ_FAILED(sq_tostring(vm, -2)))
                {
                    return sq_throwerror(vm, "Instance nexti key could not be converted into a string");
                }

                // Retrieve the converted string and string size from the stack
                const SQChar* key;
                SQInteger keySize;
                sq_getstringandsize(vm, -1, &key, &keySize); // Ignore the return value, we know there's a string here

                // Grow storage to account for '"key":'
                growStorage(jsonString, jsonStringStorageSize, jsonStringSize+3+keySize);

                // Encode the key
                jsonString[jsonStringSize++] = '"';
                memcpy(&jsonString[jsonStringSize], key, keySize);
                jsonStringSize += keySize;
                jsonString[jsonStringSize++] = '"';
                jsonString[jsonStringSize++] = ':';

                // Pop both the key, value and converted string from the stack before we recurse to reduce stack depth impact
                sq_pop(vm, 3);

                // Recurse to encode the value
                if(SQ_FAILED(encodeObject(vm, value, jsonString, jsonStringStorageSize, jsonStringSize, depth+1)))
                {
                    return SQ_ERROR;
                }

                // Grow storage to account for ','
                growStorage(jsonString, jsonStringStorageSize, jsonStringSize+1);

                jsonString[jsonStringSize++] = ',';
            }
            else
            {
                // Pop both the key and value from the stack
                sq_pop(vm, 2);
            }
        }

        // Pop the iterator from the stack (we're not done with the instance object yet)
        sq_poptop(vm);

        // If there were entries, place the closing '}' one space back to erase the trailing comma
        if(outputSizeSnapshot != jsonStringSize)
        {
            jsonString[jsonStringSize-1] = '}';
        }
        else
        {
            // Grow storage to account for '}'
            growStorage(jsonString, jsonStringStorageSize, jsonStringSize+1);

            jsonString[jsonStringSize++] = '}';
        }

        // Retrieve the last error onto and then from the stack, popping the retrieved value 
        sq_getlasterror(vm);
        HSQOBJECT subObject;
        sq_resetobject(&subObject);
        sq_getstackobj(vm, -1, &subObject);
        sq_poptop(vm);

        // If the last error was not null, nexti failed, instead, extract the instance's class
        // and iterate over this instead as a fallback
        if(!sq_isnull(subObject))
        {
            // Rewind the jsonStringSize position
            jsonStringSize = outputSizeSnapshot - 1;

            // Fetch the instance's class onto and from the stack, immediately popping the value and instance object
            sq_getclass(vm, -1);
            sq_resetobject(&subObject);
            sq_getstackobj(vm, -1, &subObject);
            sq_pop(vm, 2);

            // Recurse to encode the value (same depth as it's a fallback)
            if(SQ_FAILED(encodeObject(vm, subObject, jsonString, jsonStringStorageSize, jsonStringSize, depth)))
            {
                return SQ_ERROR;
            }
        }
        else
        {
            // Pop the instance object
            sq_poptop(vm);
        }
    }

    return SQ_OK;
}