Support JsonObject Serialization/Deserialization in OData
We’re working on a use case where we need to handle JsonObject properties within our OData model. To achieve this, we’ve implemented custom serialization and deserialization logic, but we’ve encountered a few challenges that may indicate that we are not using the right path. We wishes to expose a JsonObject as an untyped Object.
Use Case
We want to expose a property of type JsonObject (We don't know the inside shape of the JSON, that is why we wish to have a JsonObject ) in our model and have it correctly serialized/deserialized as a JSON object in OData requests/responses.
Challenges & Workarounds
Model Definition OData interprets JsonObject as IEnumerable<KeyValuePair<string, JsonNode>>, which causes it to treat the property as a list rather than a JSON object. This would make the payload format different to what we expect.
Workaround: We removed the all JsonObject from Entity and Complex type from the EDM Model, we builded the model and with the result we added back the removed properties but as untyped , which allows us to handle them as raw JSON ({}) in the payload.
Serialization
To bridge the EDM untyped object and the CLR JsonObject, we implemented a custom serializer: 1. Subclassed ODataResourceSerializer 2. Overrode CreateUntypedPropertyValue to return an ODataProperty with an ODataUntypedValue using the raw JSON string 3. This works as expected for serialization.
Deserialization
Deserialization is where we hit a blocker:
The CLR type (JsonObject) and EDM type (Untyped) mismatch causes the default deserialization to fail. We implemented a custom ODataResourceDeserializer and overrode ApplyNestedProperty to handle this. We use Delta<T> and call delta.TrySetPropertyValue() to set the property. However, during this process, we encounter an InvalidOperationException in CollectionDeserializationHelper.AddToCollectionCore, specifically when addMethod.Invoke() is called.
Is this a known limitation or bug in how OData handles untyped properties and JsonObject types? Or are we misusing the framework for this scenario?
We’ve created a repository Repo with a minimal reproduction of the issue for reference.
Any guidance or suggestions would be greatly appreciated!
@Justinlcx I think this section could help/meet your requirement. Please let me know if it doesn't work so I can dig more on your repo.
@xuzhg Thanks for your response.
I already looked at your repo. It helped shape our approach for serialization using the custom serializer from your example, and that part works well. The problem comes up during deserialization, since we're not using object as the CLR type, and that’s where things start to fall apart doesn't work as i would expect.
Our main use case is that we receive a payload from a library, and that payload is valid JSON but we don’t know the structure of the values inside.
We want to keep it as a JsonObject (from STJ) in our model so that when we serialize it, we can just pass the raw value directly to the ODataProperty for writing. We don’t want to implement a custom converter that tries to handle every possible type we’d rather let STJ do its job. We also don’t want to override how OData handles serialization/deserialization of objects or untyped values. That’s why we’re relying on the CLR type of our model to guide the behavior.
The issue is that since JsonObject implements IList<KeyValuePair<string, JsonNode>>, OData treats it as a collection. But we want it to behave more like how OData handles untyped objects. The deserialization problem happens in TrySetPropertyValue inside the Delta logic, because it sees the property as a collection, it tries to call Add, but that fails.
Wrapping the JsonObject in another class so it no longer implements IList work, but this is more a workaround. I would like to make it directly work with JsonObject.
Hello @Justinlcx currently exploring a possible new design for OData serialization aimed at efficiency. I would like for us to the new design to be able to support customizing serialization for new payload types. Since it's still at an early design/exploratory phase, your input could shape the final design.
If you have some time, could you check out this proposal: https://github.com/OData/odata.net/blob/odl-9-new-writer/docs/designs/2025-06-odata-writer-new-architecture.md and share your feedback including you would envision your scenario working with the proposed design (e.g. creating a custom writer), or adjustments that could be made to better support your scenario.
You don't have to go over the entire doc (but feel free to do so), you can get a general idea of the proposal by just reading through the Minimal Example. The proposal still hasn't addressed untyped values or dynamic properties, but it'll get there eventually.
To share feedback you could add comments to this PR's file: https://github.com/OData/odata.net/pull/3233/files#diff-034849d98432138fafab3477806fd4d100d9574614020f4718619cc3ce2ff5af or just reply directly to this issue here.
Thanks.
Hello @habbes, thank you, i will take a look.