Overview
I'm trying to write a web service using ASP.NET Core that allows clients to query and modify the state of a microcontroller. This microcontroller contains a number of systems that I model within my application - for instance, a PWM system, an actuator input system, etc.
The components of these systems all have particular properties that can be queried or modified using a JSON patch request. For example, the 4th PWM on the micro can be enabled using an HTTP request carrying {"op":"replace", "path":"/pwms/3/enabled", "value":true}
. To support this, I'm using the AspNetCore.JsonPatch
library.
My problem is that I'm trying to implement JSON Patch support for a new "CAN database" system that logically should map a definition name to a particular CAN message definition, and I'm not sure how to go about this.
Details
The diagram below models the CAN database system. A CanDatabase
instance should logically contain a dictionary of the form IDictionary<string, CanMessageDefinition>
.
To support creating new message definitions, my application should allow users to send JSON patch requests like this:
{
"op": "add",
"path": "/candb/my_new_definition",
"value": {
"template": ["...", "..."],
"repeatRate": "...",
"...": "...",
}
}
Here, my_new_definition
would define the definition name, and the object associated with value
should be deserialised to a CanMessageDefinition
object. This should then be stored as a new key-value pair in the CanDatabase
dictionary.
The issue is that path
should specify a property path which for statically-typed objects would be...well, static (an exception to this is that it allows for referencing array elements e.g. /pwms/3
as above).
What I've tried
A. The Leeroy Jenkins approach
Forget the fact that I know it won't work - I tried the implementation below (which uses static-typing only despite the fact I need to support dynamic JSON Patch paths) just to see what happens.
Implementation
internal sealed class CanDatabaseModel : DeviceComponentModel<CanDatabaseModel>
{
public CanDatabaseModel()
{
this.Definitions = new Dictionary<string, CanMessageDefinition>();
}
[JsonProperty(PropertyName = "candb")]
public IDictionary<string, CanMessageDefinition> Definitions { get; }
...
}
Test
{
"op": "add",
"path": "/candb/foo",
"value": {
"messageId": 171,
"template": [17, 34],
"repeatRate": 100,
"canPort": 0
}
}
Outcome
An InvalidCastException
is thrown at the site where I try to apply the specified changes to the JsonPatchDocument
.
Site:
var currentModelSnapshot = this.currentModelFilter(this.currentModel.Copy());
var snapshotWithChangesApplied = currentModelSnapshot.Copy();
diffDocument.ApplyTo(snapshotWithChangesApplied);
Exception:
Unable to cast object of type 'Newtonsoft.Json.Serialization.JsonDictionaryContract' to type 'Newtonsoft.Json.Serialization.JsonObjectContract'.
B. Relying on dynamic JSON Patching
A more promising plan of attack seemed to be relying on dynamic JSON patching, which involves performing patch operations on instances of ExpandoObject
. This allows you to use JSON patch documents to add, remove or replace properties since you're dealing with a dynamically-typed object.
Implementation
internal sealed class CanDatabaseModel : DeviceComponentModel<CanDatabaseModel>
{
public CanDatabaseModel()
{
this.Definitions = new ExpandoObject();
}
[JsonProperty(PropertyName = "candb")]
public IDictionary<string, object> Definitions { get; }
...
}
Test
{
"op": "add",
"path": "/candb/foo",
"value": {
"messageId": 171,
"template": [17, 34],
"repeatRate": 100,
"canPort": 0
}
}
Outcome
Making this change allows this part of my test to run without exceptions being raised, but JSON Patch has no knowledge of what to deserialise value
as, resulting in the data being stored in the dictionary as a JObject
rather than a CanMessageDefinition
:
Would it be possible to 'tell' JSON Patch how to deserialise the information by any chance? Perhaps something along the lines of using a JsonConverter
attribute on Definitions
?
[JsonProperty(PropertyName = "candb")]
[JsonConverter(...)]
public IDictionary<string, object> Definitions { get; }
Summary
- I need to support JSON patch requests that add values to a dictionary
- I've tried going down the purely-static route, which failed
- I've tried using dynamic JSON patching
- This partly worked, but my data was stored as a
JObject
type instead of the intended type - Is there an attribute (or some other technique) I can apply to my property to let it deserialise to the correct type (not an anonymous type)?
- This partly worked, but my data was stored as a
template
invalue
object? Can we movemessageId
andtemplate
to parent object? – Bumbailifftemplate
represents a CAN message payload (0-8 bytes), so it would be an array of integers.messageId
andtemplate
have to remain as they are because requests need to adhere to the JSON Patch schema as described in RFC 6902 – JanettjanettaPropertyChanged
event handler to theExpandoObject
to manually convert the newJObject
into aCanMessageDefinition
). – JanettjanettaIDictionary<T, K>
. Did you try just usingDictionary<T, K>
? I'm using that and not having any issues. – Baseman