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(),
|
||||
"string" => element.GetString(),
|
||||
"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()),
|
||||
};
|
||||
}
|
||||
|
||||
/// <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>
|
||||
|
||||
Reference in New Issue
Block a user