JsonUtility.FromJson error "document root must not follow by other values"

This error appears under the following conditions

  1. I have a JSON coming from an API, that looks like this. I have parsed this manually with JS’s JSON.parse and I am certain this is valid JSON:

    {“Items”:[{“ID”:“0fcf745c-0501-4dac-a305-360eedab52c3”}]}

  2. I auto-generated classes using http://json2csharp.com/ and those classes for models look like this:

    public class Item
    {
    public string ID { get; set; }
    }

    public class RootObject
    {
    public List Items { get; set; }
    }

  3. I am using JsonUtility.FromJson to parse this JSON data from a string into this RootObject model:

    var data = System.Text.RegularExpressions.Regex.Unescape(
    Encoding.ASCII.GetString(responseObject.Response.Payload.ToArray())
    );
    // Confirmed: “data” when logged looks exactly like this:
    // “{“Items”:[{“ID”:“0fcf745c-0501-4dac-a305-360eedab52c3”}]}”
    RootObject data = JsonUtility.FromJson(data);

But nonetheless, I am relentlessly getting this error. I tried also manually using the System.Serializable flag and removing get; set; generated, as well as changing between structs and classes:

	[System.Serializable]
	public class Item
	{
		public string ID;
	}

	[System.Serializable]
	public class RootObject
	{
		public List<Item> Items;
	}

It does not appear to make any sense as to what is going on here. Additionally, this error is very unclear. The text says “The document root must not follow by other values.” Document root I assume means “Items”, in my case, and it most certainly is not followed by any other properties. It is of course “followed by” a value, i.e. the array of objects after it, but what good is a key-value pair without a value? :slight_smile:

Here is the stack:

System.ArgumentException: JSON parse error: The document root must not follow by other values.
  at (wrapper managed-to-native) UnityEngine.JsonUtility.FromJsonInternal(string,object,System.Type)
  at UnityEngine.JsonUtility.FromJson (System.String json, System.Type type) [0x0005c] in /Users/builduser/buildslave/unity/build/Modules/JSONSerialize/Public/JsonUtility.bindings.cs:42 
  at UnityEngine.JsonUtility.FromJson[T] (System.String json) [0x00001] in /Users/builduser/buildslave/unity/build/Modules/JSONSerialize/Public/JsonUtility.bindings.cs:30 

If anyone has any insight into what I may be doing wrong here, it would be greatly appreciated. IMHO I think this might need to be a bug report filed to Unity, however, as I have gone through great lengths to ensure this JSON is valid. However, I am thinking the more likely scenario is that this error does not mean what it says (what is says doesn’t really mean anything anyway, as far as I can tell), and maybe there is something else wrong with it?

FWIW I also tried using UTF8 encoding instead of ASCII, and the reason I am using that Regex unescape line is because the API is sending escaped backslashes, which for some reason Unity doesn’t know how to handle :slight_smile: if it helps at all, this JSON is a request being made through AWS SDK Lambda to an API Gateway endpoint.

Cheers and thanks friends,
Keith

All right, for anyone in the future who is having trouble with getting Unity’s AWS SDK to work with JsonUtility.FromJson, I think I sorted what the problem is. This is going to be complicated to explain but here it is.

In my case, I have a Lambda running which queries Aurora RDS for a MySQL record using NodeJS. The Lambda returns a stringified array. JsonUtility most certainly does not know how to handle arrays, so I changed the response to be like

{ "Items": [ .... ] }

This way JsonUtility would be able to behave. Unfortunately, this means that the AWS SDK’s returned value was not an array, and I was using this function

Encoding.ASCII.GetString(responseObject.Response.Payload.ToArray())

Now, when you run this function, if you inspect the output, it actually “looks” OK. However, there seems to be something about using this ToArray() function on a JSON string that isn’t an array that really doesn’t work on stringified objects (note: Someone much smarter than me might be able to explain this). ToArray does not turn anything into an array, but just takes a binary stream (payload) and turns into a byte array.

At this point, I was getting all sorts of problems, so what I decided to do was to change my lambda to keep returning an Array like it did before, and instead handle the wrapping on C# side. This is what I came up with:

#region Lambda
/// <summary>
/// Wraps a JSON array in top-level object with property "Items", for use with Models.Collection<T>
/// </summary>
/// <param name="responseArray">JSON array from Lambda AWS SDK JSON response.</param>
/// <returns>string for deserialization</returns>
private string WrapResponseArray (string responseArray)
{
    // We need to substring off the double quotes that are around response array.
    return "{ \"Items\": " + responseArray.Substring(1, responseArray.Length - 2) + "}";
}

#region Invoke
/// <summary>
/// Example method to demostrate Invoke. Invokes the Lambda function with the specified
/// function name (e.g. helloWorld) with the parameters specified in the Event JSON.
/// Because no InvokationType is specified, the default 'RequestResponse' is used, meaning
/// that we expect the AWS Lambda function to return a value.
/// </summary>
public void Invoke<T>(string payload, object sqlParams = null, Action<Models.Collection<T>> callback = null)
{
    LambdaQuery lambdaQuery = new LambdaQuery();
    lambdaQuery.sqlStatement = payload;

    if (sqlParams != null)
    {
        lambdaQuery.sqlParams = JsonUtility.ToJson(sqlParams);
    }

    string query = JsonUtility.ToJson(lambdaQuery);

    Client.InvokeAsync(new Amazon.Lambda.Model.InvokeRequest()
    {
        FunctionName = RDSFunctionName,
        Payload = query
    },
    (responseObject) =>
    {
        if (responseObject.Exception == null)
        {
            Debug.Log("Successful InvokeAsync");
            Debug.Log(Encoding.ASCII.GetString(responseObject.Response.Payload.ToArray()) + "

");

            string arrData = Encoding.ASCII.GetString(responseObject.Response.Payload.ToArray());
            // Debug.Log("arrData");
            // Debug.Log(arrData);
            string unescaped = System.Text.RegularExpressions.Regex.Unescape(arrData);
            // Debug.Log("unescaped");
            // Debug.Log(unescaped);
            string wrapped = WrapResponseArray(unescaped);
            // Debug.Log("wrapped");
            // Debug.Log(wrapped);
            try 
            {
                Models.Collection<T> data = JsonUtility.FromJson<Models.Collection<T>>(wrapped);
                Debug.Log("data");
                Debug.Log(data);
                Debug.Log("callback~!");
                callback?.Invoke(data);
            }
            catch (Exception e)
            {
                Debug.LogError(e);
            }
        }
        else
        {
            Debug.Log(
                responseObject.Exception + "

"
);
}
}
);
}
#endregion
#endregion

And I made a little generic wrapper model that I put my other models into:

/**
 * Generic model wrapper that puts an array into items.
 */
[Serializable]
public struct Collection<T>
{
    public List<T> Items;
}

Hope this helps someone in the future!