CoplayDev/unity-mcp · error · JsonSerializationException

Expected JSON object or null when deserializing Matrix4x4, g

Error message

Expected JSON object or null when deserializing Matrix4x4, got '{reader.TokenType}'.

What it means

Thrown by Matrix4x4Converter.ReadJson when the current JSON token is not StartObject and not Null. Matrix4x4 must be deserialized from a JSON object with m00-m33 fields (or null for a zero matrix). Arrays, scalars, or strings are rejected. This differs from the Vector converters which accept arrays.

Source

Thrown at MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs:245

            writer.WritePropertyName("m13"); writer.WriteValue(value.m13);
            writer.WritePropertyName("m20"); writer.WriteValue(value.m20);
            writer.WritePropertyName("m21"); writer.WriteValue(value.m21);
            writer.WritePropertyName("m22"); writer.WriteValue(value.m22);
            writer.WritePropertyName("m23"); writer.WriteValue(value.m23);
            writer.WritePropertyName("m30"); writer.WriteValue(value.m30);
            writer.WritePropertyName("m31"); writer.WriteValue(value.m31);
            writer.WritePropertyName("m32"); writer.WriteValue(value.m32);
            writer.WritePropertyName("m33"); writer.WriteValue(value.m33);
            writer.WriteEndObject();
        }

        public override Matrix4x4 ReadJson(JsonReader reader, Type objectType, Matrix4x4 existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
                return new Matrix4x4(); // Return zero matrix for null (consistent with missing field defaults)

            if (reader.TokenType != JsonToken.StartObject)
                throw new JsonSerializationException($"Expected JSON object or null when deserializing Matrix4x4, got '{reader.TokenType}'.");

            JObject jo = JObject.Load(reader);
            var matrix = new Matrix4x4();
            matrix.m00 = jo["m00"]?.Value<float>() ?? 0f;
            matrix.m01 = jo["m01"]?.Value<float>() ?? 0f;
            matrix.m02 = jo["m02"]?.Value<float>() ?? 0f;
            matrix.m03 = jo["m03"]?.Value<float>() ?? 0f;
            matrix.m10 = jo["m10"]?.Value<float>() ?? 0f;
            matrix.m11 = jo["m11"]?.Value<float>() ?? 0f;
            matrix.m12 = jo["m12"]?.Value<float>() ?? 0f;
            matrix.m13 = jo["m13"]?.Value<float>() ?? 0f;
            matrix.m20 = jo["m20"]?.Value<float>() ?? 0f;
            matrix.m21 = jo["m21"]?.Value<float>() ?? 0f;
            matrix.m22 = jo["m22"]?.Value<float>() ?? 0f;
            matrix.m23 = jo["m23"]?.Value<float>() ?? 0f;
            matrix.m30 = jo["m30"]?.Value<float>() ?? 0f;
            matrix.m31 = jo["m31"]?.Value<float>() ?? 0f;
            matrix.m32 = jo["m32"]?.Value<float>() ?? 0f;

View on GitHub (pinned to c21bf496bc)

Solutions

  1. Send Matrix4x4 as a JSON object with m00-m33 keys: {"m00": 1, "m01": 0, ..., "m33": 1}.
  2. For an identity matrix, send null (resolves to zero matrix) or build the full object.
  3. Do NOT use array form for Matrix4x4 (unlike Vector2/3/4 which accept arrays).
  4. If passing a TRS matrix, serialize it as an object with all 16 named elements.

Example fix

// before
params = {"matrix": [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]}
// after
params = {"matrix": {"m00":1,"m01":0,"m02":0,"m03":0,"m10":0,"m11":1,"m12":0,"m13":0,"m20":0,"m21":0,"m22":1,"m23":0,"m30":0,"m31":0,"m32":0,"m33":1}}
Defensive patterns

Strategy: type-guard

Validate before calling

def is_valid_matrix4x4(val) -> bool:
    # Matrix4x4 MUST be an object with m00-m33, NOT an array
    if not isinstance(val, dict):
        return False
    expected = {f'm{r}{c}' for r in range(4) for c in range(4)}
    return expected.issubset(val.keys())

# Identity matrix template:
def identity_matrix():
    m = {}
    for r in range(4):
        for c in range(4):
            m[f'm{r}{c}'] = 1.0 if r == c else 0.0
    return m

Type guard

function isMatrix4x4(val: unknown): val is Record<string, number> | null {
  if (val === null) return true;
  if (!val || typeof val !== 'object' || Array.isArray(val)) return false;
  const keys = ['m00','m01','m02','m03','m10','m11','m12','m13','m20','m21','m22','m23','m30','m31','m32','m33'];
  return keys.every(k => k in (val as Record<string, unknown>));
}

Try / catch

try
{
    var matrix = serializer.Deserialize<Matrix4x4>(reader);
}
catch (JsonSerializationException ex) when (ex.Message.Contains("Expected JSON object or null when deserializing Matrix4x4"))
{
    return new ErrorResponse("Matrix4x4 must be a JSON object with m00-m33 keys, not an array.");
}

Prevention

When it happens

Trigger: Deserializing a Matrix4x4 field from a JSON array (e.g. [[..],[..],..]) or a scalar value. The converter only accepts object form with m00-m33 keys or JSON null. Passing a nested array of 16 floats triggers this.

Common situations: Client sends matrix as a flat or nested array of 16 floats; AI model outputs a 4x4 grid as arrays; confusion with the Vector converters which DO accept arrays; shader TRS matrix sent in array form.

Related errors


AI-assisted analysis of CoplayDev/unity-mcp@c21bf496bc (2026-08-13). Data as JSON: /api/errors/adfa8e92f76c418a. Report an issue: GitHub.