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;
}