You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
After a save/load round trip, ChangeMetadata.CustomData values come back as JsonElement, so casting them to their original types throws InvalidCastException #120
#77 made a reloaded command keep its ChangeMetadata, including CustomData. The dictionary is typed IReadOnlyDictionary<string, object> (UndoRedo/Models/ChangeMetadata.cs:18), though, so System.Text.Json deserializes every value as a System.Text.Json.JsonElement. Values the app stored as int, string, DateTime and so on are no longer those types after LoadStateAsync. The keys survive, but every value changes type.
The regression test added for #77 (SerializationTests.cs:171) compares CustomData["author"].ToString(). That hides the problem, because JsonElement.ToString() happens to return the raw string.
line: System.Text.Json.JsonElement = 42
name: System.Text.Json.JsonElement = x
Unhandled: InvalidCastException: Unable to cast object of type 'System.Text.Json.JsonElement' to type 'System.Int32'.
Why it matters
CustomData is where apps keep things like a caret line or a selection id, which they read back to drive navigation or history UI. Code that casts or pattern-matches those values (is int line) works before a save and load, then throws or silently stops matching afterwards. Because the type depends on whether the history was reloaded, this is easy to miss in testing.
Suggested fix / acceptance criteria
On deserialize, convert each JsonElement in CustomData back into a plain CLR value, for example with a custom converter on ChangeMetadata.CustomData:
string → string
number → long, or double when it isn't a whole number
true/false → bool
array → List<object>
object → Dictionary<string, object>
Alternatively, store each value's type name next to it and deserialize to that type.
Document which types survive a round trip, for example that an int comes back as a long.
What's wrong
#77 made a reloaded command keep its
ChangeMetadata, includingCustomData. The dictionary is typedIReadOnlyDictionary<string, object>(UndoRedo/Models/ChangeMetadata.cs:18), though, so System.Text.Json deserializes every value as aSystem.Text.Json.JsonElement. Values the app stored asint,string,DateTimeand so on are no longer those types afterLoadStateAsync. The keys survive, but every value changes type.The regression test added for #77 (
SerializationTests.cs:171) comparesCustomData["author"].ToString(). That hides the problem, becauseJsonElement.ToString()happens to return the raw string.Repro
Observed:
Why it matters
CustomDatais where apps keep things like a caret line or a selection id, which they read back to drive navigation or history UI. Code that casts or pattern-matches those values (is int line) works before a save and load, then throws or silently stops matching afterwards. Because the type depends on whether the history was reloaded, this is easy to miss in testing.Suggested fix / acceptance criteria
JsonElementinCustomDataback into a plain CLR value, for example with a custom converter onChangeMetadata.CustomData:stringlong, ordoublewhen it isn't a whole numberboolList<object>Dictionary<string, object>intcomes back as along.ToString(), and add a numeric entry.