fix(inbound): materialize array params as typed lists, not JsonElement
An inbound /api array parameter was materialized as List<object?> whose elements were raw System.Text.Json.JsonElement. When such a value is routed Central->Site and a template script assigns it to a List-typed Galaxy attribute (recv.Attributes[name] = Parameters[name]), the script-side encode stalls (the attribute codec JSON-serializing JsonElement items) and the array write never reaches the DCL — the Ipsen MoveIn array writes hung 30s while scalars succeeded. ParameterValidator.MaterializeArray now builds a strongly-typed list per the declared element schema (List<string>/long/double/bool); arrays with no declared scalar element type materialize each element to its CLR value (MaterializeJsonValue) so no raw JsonElement survives. Typed lists serialize cleanly across nodes and encode to a canonical JSON array, which the InstanceActor decodes back to the typed list for the device write.
This commit is contained in:
@@ -114,10 +114,78 @@ public static class ParameterValidator
|
|||||||
"number" => element.GetDouble(),
|
"number" => element.GetDouble(),
|
||||||
"string" => element.GetString(),
|
"string" => element.GetString(),
|
||||||
"object" => JsonSerializer.Deserialize<Dictionary<string, object?>>(element.GetRawText()),
|
"object" => JsonSerializer.Deserialize<Dictionary<string, object?>>(element.GetRawText()),
|
||||||
"array" => JsonSerializer.Deserialize<List<object?>>(element.GetRawText()),
|
"array" => MaterializeArray(element, schema.Items),
|
||||||
_ => JsonSerializer.Deserialize<object?>(element.GetRawText()),
|
_ => JsonSerializer.Deserialize<object?>(element.GetRawText()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Materializes a JSON array to a STRONGLY-TYPED list (List<string>,
|
||||||
|
/// List<long>, List<double>, List<bool>) per the element schema,
|
||||||
|
/// rather than a <c>List<object?></c> of raw <see cref="JsonElement"/>. The
|
||||||
|
/// raw-JsonElement form is not cleanly Akka-serializable when the parameter is
|
||||||
|
/// routed Central→Site, and a script writing it to a List attribute stalls when
|
||||||
|
/// the attribute codec tries to JSON-serialize JsonElement items. A typed list
|
||||||
|
/// serializes cleanly across nodes and encodes to a canonical JSON array, which
|
||||||
|
/// the InstanceActor decodes back to the typed list for the device write.
|
||||||
|
/// </summary>
|
||||||
|
private static object? MaterializeArray(JsonElement element, InboundApiSchema? items)
|
||||||
|
{
|
||||||
|
// When the element type is a declared scalar, build a STRONGLY-TYPED list
|
||||||
|
// (List<string>/long/double/bool) — the cleanest form to route Central→Site
|
||||||
|
// and to encode to a canonical JSON array for a List attribute write.
|
||||||
|
switch (items?.Type)
|
||||||
|
{
|
||||||
|
case "integer":
|
||||||
|
{
|
||||||
|
var list = new List<long>(element.GetArrayLength());
|
||||||
|
foreach (var e in element.EnumerateArray()) list.Add(e.GetInt64());
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
case "number":
|
||||||
|
{
|
||||||
|
var list = new List<double>(element.GetArrayLength());
|
||||||
|
foreach (var e in element.EnumerateArray()) list.Add(e.GetDouble());
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
case "boolean":
|
||||||
|
{
|
||||||
|
var list = new List<bool>(element.GetArrayLength());
|
||||||
|
foreach (var e in element.EnumerateArray()) list.Add(e.GetBoolean());
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
case "string":
|
||||||
|
{
|
||||||
|
var list = new List<string>(element.GetArrayLength());
|
||||||
|
foreach (var e in element.EnumerateArray())
|
||||||
|
list.Add(e.ValueKind == JsonValueKind.Null ? string.Empty : e.GetString() ?? string.Empty);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
// No declared (or non-scalar) element type: materialize each element
|
||||||
|
// to its CLR value so no raw JsonElement survives (raw JsonElement is
|
||||||
|
// not cleanly Akka-serializable and stalls the List-attribute encode).
|
||||||
|
var list = new List<object?>(element.GetArrayLength());
|
||||||
|
foreach (var e in element.EnumerateArray()) list.Add(MaterializeJsonValue(e));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Recursively converts a <see cref="JsonElement"/> to a plain CLR value
|
||||||
|
/// (string/long/double/bool/null, or nested List/Dictionary) — never a JsonElement.</summary>
|
||||||
|
private static object? MaterializeJsonValue(JsonElement e) => e.ValueKind switch
|
||||||
|
{
|
||||||
|
JsonValueKind.String => e.GetString(),
|
||||||
|
JsonValueKind.Number => e.TryGetInt64(out var l) ? l : e.GetDouble(),
|
||||||
|
JsonValueKind.True => true,
|
||||||
|
JsonValueKind.False => false,
|
||||||
|
JsonValueKind.Null => null,
|
||||||
|
JsonValueKind.Array => e.EnumerateArray().Select(MaterializeJsonValue).ToList(),
|
||||||
|
JsonValueKind.Object => e.EnumerateObject().ToDictionary(p => p.Name, p => MaterializeJsonValue(p.Value)),
|
||||||
|
_ => e.GetRawText(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user