Hello, I need help. I’ve tried to fix the issue, but I’m not sure how. I implemented a chat feature from Google, and it works correctly in the Unity editor. However, when I build the project for iOS and test app it says: Error deserializing JSON credential Data . In Xcode, I can see the JSON file in the “raw” folder, and everything looks correct. But for some reason, it’s not working on iOS. Can you help me figure out what might be going wrong?
using System;
using System.IO;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using Google.Apis.Auth.OAuth2;
public class GoogleCloudAuthHelper : MonoBehaviour
{
public string apiKeyPath = "service-account";
private GoogleCredential _credential;
private string _accessToken;
private async void Awake()
{
await InitializeCredential();
}
private async Task InitializeCredential()
{
try
{
Debug.Log($"Attempting to load service account JSON file from Resources, StreamingAssets, or Raw folder");
// Try loading from Resources
string resourcePath = Path.GetFileNameWithoutExtension(apiKeyPath);
TextAsset jsonKeyAsset = Resources.Load<TextAsset>(resourcePath);
if (jsonKeyAsset != null)
{
Debug.Log("Service account JSON file loaded successfully from Resources.");
await LoadCredentialFromTextAsset(jsonKeyAsset);
}
else
{
// Try loading from StreamingAssets using UnityWebRequest (important for iOS)
string streamingAssetsPath = Path.Combine(Application.streamingAssetsPath, apiKeyPath + ".json");
// Use UnityWebRequest to load the file (necessary for iOS)
UnityWebRequest request = UnityWebRequest.Get(streamingAssetsPath);
await request.SendWebRequest();
if (request.result == UnityWebRequest.Result.Success)
{
Debug.Log("Service account JSON file loaded from StreamingAssets.");
string json = request.downloadHandler.text;
// Process the JSON using memory stream
using (var jsonKeyStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(json)))
{
_credential = GoogleCredential.FromStream(jsonKeyStream)
.CreateScoped(new[] { "https://www.googleapis.com/auth/cloud-platform" });
}
}
else
{
// Try loading from Raw folder if StreamingAssets failed
string rawPath = Path.Combine(Application.dataPath, "Raw", apiKeyPath + ".json");
if (File.Exists(rawPath))
{
Debug.Log("Service account JSON file loaded from Raw folder.");
await LoadCredentialFromFile(rawPath);
}
else
{
throw new FileNotFoundException($"Service account JSON file not found in Resources, StreamingAssets, or Raw folder.");
}
}
}
Debug.Log("Google Credential initialized successfully.");
// Obtain the access token
_accessToken = await GetAccessTokenAsync();
}
catch (Exception ex)
{
Debug.LogError($"Failed to initialize Google credentials: {ex.Message}");
}
}
private async Task LoadCredentialFromTextAsset(TextAsset jsonKeyAsset)
{
using (var jsonKeyStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(jsonKeyAsset.text)))
{
_credential = GoogleCredential.FromStream(jsonKeyStream)
.CreateScoped(new[] { "https://www.googleapis.com/auth/cloud-platform" });
}
}
private async Task LoadCredentialFromFile(string filePath)
{
using (var jsonKeyStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
_credential = GoogleCredential.FromStream(jsonKeyStream)
.CreateScoped(new[] { "https://www.googleapis.com/auth/cloud-platform" });
}
}
public async Task<string> GetAccessTokenAsync()
{
if (_credential == null)
{
Debug.LogError("Google Credential is not initialized.");
return null;
}
try
{
// Get the access token from the underlying credential
var tokenResponse = await _credential.UnderlyingCredential.GetAccessTokenForRequestAsync();
Debug.Log("Access token obtained successfully.");
return tokenResponse;
}
catch (Exception ex)
{
Debug.LogError($"Failed to obtain access token: {ex.Message}");
throw;
}
}
public GoogleCredential GetCredential()
{
if (_credential == null)
{
Debug.LogError("Google Credential is not initialized.");
}
return _credential;
}
public string GetStoredAccessToken()
{
return _accessToken;
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using UnityEngine;
using Newtonsoft.Json;
using System.Collections.Generic;
using UnityEngine.Networking;
public class ChatClientUnity : MonoBehaviour
{
private HttpClient _client;
public string projectId;
public string modelId;
public string locationId;
private GoogleCloudAuthHelper _authHelper;
private void Awake()
{
_client = new HttpClient();
_authHelper = gameObject.AddComponent<GoogleCloudAuthHelper>();
}
public async Task<string> Chat(string text, string context, Example[] examples)
{
try
{
var accessToken = await _authHelper.GetAccessTokenAsync();
var endpoint = $"https://us-central1-aiplatform.googleapis.com/v1/projects/{projectId}/locations/{locationId}/publishers/google/models/{modelId}:predict";
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var messages = new List<object>
{
new { author = "user", content = text }
};
var request = new
{
instances = new[]
{
new
{
context = context,
examples = examples,
messages = messages
}
}
};
var jsonContent = new StringContent(JsonConvert.SerializeObject(request));
jsonContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await _client.PostAsync(endpoint, jsonContent);
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
var responseJson = JsonConvert.DeserializeObject<PredictionResponse>(responseContent);
return responseJson.predictions[0].candidates[0].content.Trim();
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
throw new Exception($"API communication error: {response.StatusCode}, {errorContent}");
}
}
catch (Exception ex)
{
Debug.LogError($"HttpClient failed: {ex.Message}. Attempting with UnityWebRequest.");
try
{
// Fallback to UnityWebRequest
var accessToken = await _authHelper.GetAccessTokenAsync();
var endpoint = $"https://us-central1-aiplatform.googleapis.com/v1/projects/{projectId}/locations/{locationId}/publishers/google/models/{modelId}:predict";
var messages = new List<object>
{
new { author = "user", content = text }
};
var request = new
{
instances = new[]
{
new
{
context = context,
examples = examples,
messages = messages
}
}
};
string jsonData = JsonConvert.SerializeObject(request);
using (UnityWebRequest webRequest = new UnityWebRequest(endpoint, "POST"))
{
byte[] jsonToSend = new System.Text.UTF8Encoding().GetBytes(jsonData);
webRequest.uploadHandler = new UploadHandlerRaw(jsonToSend);
webRequest.downloadHandler = new DownloadHandlerBuffer();
webRequest.SetRequestHeader("Authorization", "Bearer " + accessToken);
webRequest.SetRequestHeader("Content-Type", "application/json");
await webRequest.SendWebRequest();
if (webRequest.result == UnityWebRequest.Result.Success)
{
string responseText = webRequest.downloadHandler.text;
var responseJson = JsonConvert.DeserializeObject<PredictionResponse>(responseText);
return responseJson.predictions[0].candidates[0].content.Trim();
}
else
{
throw new Exception($"UnityWebRequest error: {webRequest.responseCode}, {webRequest.error}");
}
}
}
catch (Exception innerEx)
{
Debug.LogError($"Both HttpClient and UnityWebRequest failed: {innerEx.Message}");
throw;
}
}
}
[Serializable]
public class Example
{
public Message input { get; set; }
public Message output { get; set; }
}
public class Message
{
public string content { get; set; }
}
[Serializable]
public class PredictionResponse
{
public Prediction[] predictions { get; set; }
}
[Serializable]
public class Prediction
{
public Candidate[] candidates { get; set; }
}
[Serializable]
public class Candidate
{
public string content { get; set; }
}
}