When the playfab client API calls the locally running azure functions cloudscript from Unity, the response always gives an error
SerializationException: Invalid JSON string PlayFab.Json.PlayFabSimpleJson.DeserializeObject (System.String json) (at Assets/PlayFabSDK/Shared/Internal/SimpleJson.cs:570) PlayFab.Json.PlayFabSimpleJson.DeserializeObject (System.String json, System.Type type, PlayFab.Json.IJsonSerializerStrategy jsonSerializerStrategy) (at Assets/PlayFabSDK/Shared/Internal/SimpleJson.cs:602) PlayFab.Json.PlayFabSimpleJson.DeserializeObject[T] (System.String json, PlayFab.Json.IJsonSerializerStrategy jsonSerializerStrategy) (at Assets/PlayFabSDK/Shared/Internal/SimpleJson.cs:610) PlayFab.Json.SimpleJsonInstance.DeserializeObject[T] (System.String json) (at Assets/PlayFabSDK/Shared/Internal/ISerializer.cs:85) PlayFab.Internal.PlayFabUnityHttp.OnResponse (System.String response, PlayFab.Internal.CallRequestContainer reqContainer) (at Assets/PlayFabSDK/Shared/Internal/PlayFabHttp/PlayFabUnityHttp.cs:176) UnityEngine.Debug:LogException(Exception) PlayFab.Internal.PlayFabUnityHttp:OnResponse(String, CallRequestContainer) (at
I have even tried returning a simple string from the function to the client, but it gives the same error. I am using the latest ExecuteFunction.cs that is provided here https://github.com/PlayFab/pf-af-devfuncs/blob/main/csharp/ExecuteFunction.cs, and have made a Github issue here https://github.com/PlayFab/pf-af-devfuncs/issues/12
It works fine when calling the live function on azure cloud, it's just locally it doesn't work.
This is very important as I need to develop locally without having to affect production! The local workflow is already a bit terrible, I have to create and delete a json file just to get it to use the local functions URL, I can't even change it from code!!!
Could you please provide the relevant code snippet of your Azure function and your unity client for us to diagnose? Here is a similar thread that you could look into: https://community.playfab.com/questions/8487/serializationexception-invalid-json-string-in-unit.html.
It doesn't matter what I return from the azure function, a string, an empty object, an object with only alphanumeric keys and values, everything gives this error!
I have done a test using the code and followed this document Tutorial: Local debugging for Cloudscript using Azure Functions to debug locally. But I cannot reproduce this error. This error seems to be reported from the Unity. Which version of the Unity, the PlayFab SDK and the PlayFab Unity Editor Extension you were using? Could you provide the relevant Unity code snippet for us to diagnose?
Answer by Jay Zuo · Dec 16, 2021 at 09:18 AM
The issue here might to be that while local debugging, Unity sets Accept-Encoding to "deflate, gzip" (This seems to be a black box, as in Rick's test, the Accept-Encoding is always set to "identity", so he cannot reproduce this issue and according to Unity doc, it's not recommended to set accept-encoding header and we should leave it for automatic handling).
When the request's Accept-Encoding accepts "gzip", ExecuteFunction will return compressed response body. However, it doesn't set corresponding Content-Encoding header, so Unity doesn't decompress the response body for you, which caused it to be an unreadable sting that can't be deserialized. And eventually, Unity SDK throws an "Invalid JSON string" error.
To fix this, we can modify CompressResponseBody method and add Content-Encoding header like:
// If client accepts gzip, compress if (encodings.Contains("gzip", StringComparer.OrdinalIgnoreCase)) { using (var stream = new MemoryStream()) { using (var gZipStream = new GZipStream(stream, CompressionLevel.Fastest, false)) { gZipStream.Write(responseBytes, 0, responseBytes.Length); } responseBytes = stream.ToArray(); } var content = new ByteArrayContent(responseBytes); content.Headers.ContentEncoding.Add("gzip"); return content; }
I've also submitted a PR https://github.com/PlayFab/pf-af-devfuncs/pull/13. Hope it helps.
Answer by esk · Aug 20, 2021 at 02:02 AM
Unity 2020.3.15f2, PlayFab 2.112.210816 SDK, basically latest everything.
The code snippet is essentially the most basic executefunction example given by the docs,
PlayFabCloudScriptAPI.ExecuteFunction(new ExecuteFunctionRequest() { Entity = new PlayFab.CloudScriptModels.EntityKey() { Id = PlayFabSettings.staticPlayer.EntityId, //Get this from when you logged in, Type = PlayFabSettings.staticPlayer.EntityType, //Get this from when you logged in }, FunctionName = Constants.CloudScriptFunctionNames.PublishLevel, //This should be the name of your Azure Function that you created. FunctionParameter = payload, //This is the data that you would want to pass into your function. GeneratePlayStreamEvent = false //Set this to true if you would like this call to show up in PlayStream }, (ExecuteFunctionResult result) => { if (result.FunctionResultTooLarge ?? false) { Debug.Log("This can happen if you exceed the limit that can be returned from an Azure Function, See PlayFab Limits Page for details."); t.TrySetException(new System.Exception("Function result too large")); return; } Debug.Log($"The {result.FunctionName} function took {result.ExecutionTimeMilliseconds} to complete"); Debug.Log($"Result: {result.FunctionResult.ToString()}"); }, (PlayFabError error) => { Debug.Log($"PlayFab Error: {error.GenerateErrorReport()}"); });
I have used the code snippet you provide and done a test. I got the result successfully and I still cannot reproduce the issue.
y unity version is 2020.3.8f1, my PlayFabSDK version is 2.98.201027 and my PlayFab Editor Extensions version is 2.100.201207.
Have you used the PlayFab Editor Extensions in your project? If so, what is the version of it?
I will try on the version you mentioned. In the mean time, as it is hard to tell which part was causing the issue. Please follow the Debugging C# code in Unity to find out the specific part that was causing the issue.
Im using editor extensions version 2.110.210628. It may be worth pointing out that i'm on MacOS (Big Sur, intel), and using the Azure VSCode extension.
What are you returning from the azure function? I tried returning a class, and a string, neither of which work locally (but do when hosted)
Could you also share the executefunction.cs that you are using? maybe that's the issue.
Answer by Rick Chen · Aug 27, 2021 at 07:56 AM
Sorry for the late reply. Currently it is hard for us to do the test. My Azure function was returning the result (object) of GetEntityProfile API call. Here is the codesnippet of my local Azure Function:
[FunctionName("ExecuteFunction")] public static async Task<HttpResponseMessage> ExecuteFunction( [HttpTrigger(AuthorizationLevel.Function, "post", Route = "CloudScript/ExecuteFunction")] HttpRequest request, ILogger log) { // Extract the caller's entity token string callerEntityToken = request.Headers["X-EntityToken"]; // Extract the request body and deserialize string body = await DecompressHttpBody(request); var execRequest = PlayFabSimpleJson.DeserializeObject<ExecuteFunctionRequest>(body); EntityKey entityKey = null; if (execRequest.Entity != null) { entityKey = new EntityKey { Id = execRequest.Entity?.Id, Type = execRequest.Entity?.Type }; } // Create a FunctionContextInternal as the payload to send to the target function var functionContext = new FunctionContextInternal { CallerEntityProfile = await GetEntityProfile(callerEntityToken, entityKey), TitleAuthenticationContext = new TitleAuthenticationContext { Id = Environment.GetEnvironmentVariable(TITLE_ID, EnvironmentVariableTarget.Process), EntityToken = await GetTitleEntityToken() }, FunctionArgument = execRequest.FunctionParameter }; // Serialize the request to the azure function and add headers var functionRequestContent = new StringContent(PlayFabSimpleJson.SerializeObject(functionContext)); functionRequestContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); functionRequestContent.Headers.Add("X-EntityToken",callerEntityToken); var azureFunctionUri = "https://[your title id].playfabapi.com/Profile/GetProfile"; var sw = new Stopwatch(); sw.Start(); // Execute the local azure function using (var functionResponseMessage = await httpClient.PostAsync(azureFunctionUri, functionRequestContent)) { sw.Stop(); long executionTime = sw.ElapsedMilliseconds; if (!functionResponseMessage.IsSuccessStatusCode) { throw new Exception($"An error occured while executing the target function locally: FunctionName: {execRequest.FunctionName}, HTTP Status Code: {functionResponseMessage.StatusCode}."); } // Extract the response content using (var functionResponseContent = functionResponseMessage.Content) { // Prepare a response to reply back to client with and include function execution results var functionResult = new ExecuteFunctionResult { FunctionName = execRequest.FunctionName, FunctionResult = await ExtractFunctionResult(functionResponseContent), ExecutionTimeMilliseconds = (int)executionTime, FunctionResultTooLarge = false }; // Reply back to client with final results var output = new PlayFabJsonSuccess<ExecuteFunctionResult> { code = 200, status = "OK", data = functionResult }; // Serialize the output and return it var outputStr = PlayFabSimpleJson.SerializeObject(output); return new HttpResponseMessage { Content = new ByteArrayContent(CompressResponseBody(output, request)), StatusCode = HttpStatusCode.OK }; } } }
Here is my code snippet of how the local function is called in Unity:
PlayFabCloudScriptAPI.ExecuteFunction(new ExecuteFunctionRequest() { Entity = new PlayFab.CloudScriptModels.EntityKey() { Id = PlayFabSettings.staticPlayer.EntityId, //Get this from when you logged in, Type = PlayFabSettings.staticPlayer.EntityType, //Get this from when you logged in }, FunctionName = "Constants.CloudScriptFunctionNames.PublishLevel", //This should be the name of your Azure Function that you created. FunctionParameter = "payload", //This is the data that you would want to pass into your function. GeneratePlayStreamEvent = false //Set this to true if you would like this call to show up in PlayStream }, (ExecuteFunctionResult result) => { if (result.FunctionResultTooLarge ?? false) { Debug.Log("This can happen if you exceed the limit that can be returned from an Azure Function, See PlayFab Limits Page for details."); //t.TrySetException(new System.Exception("Function result too large")); return; } Debug.Log($"The {result.FunctionName} function took {result.ExecutionTimeMilliseconds} to complete"); Debug.Log($"Result: {result.FunctionResult.ToString()}"); }, (PlayFabError error) => { Debug.Log($"PlayFab Error: {error.GenerateErrorReport()}"); });
As the resources for us is limited, it could take a while for us to set up the test. Your patience is appreciated.
Answer by esk · Nov 22, 2021 at 05:33 AM
The solution I finally figured out is to set "Accept-Encoding" to "identity" in the extraHeaders argument when calling ExecuteFunction. There seems to be some problem with the gzip encoding, hopefully the PlayFab team will figure it out as this seems to be an issue quite a few people are having. I was personally having this issue when developing locally, but now I seem to have it when using the cloud hosted Azure Functions directly too, and I don't know why!
Answer by Michael Urvan · Nov 23, 2021 at 01:29 AM
wow thank you for saving me so much time!
Since the compression only breaks the local testing - to fix this (so you don't have to make all your client calls pass the Accept-Encoding which might be breaking the normal cloud azure calls too), just modify your LocalExecuteFunction() code that they told us to add for local testing:
for DecompressHttpBody()
on the 3rd line down change it to:
if (true) //if (string.IsNullOrWhiteSpace(encoding))
for CompressResponseBody()
on the 8th line down change it to:
if (true)//if (string.IsNullOrEmpty(encodingsString))
CloudScript execution API requests issued (triggered action)), bug or how does it works? 1 Answer
title_player_account PlayFabId does not work with cloud script 1 Answer
CloudScript Error in Player Dashboard 2 Answers
CloudScript won't deploy last revision 1 Answer
Intermittent 500 Error on Azure Functions called via Playfab 1 Answer