C
C#•3w ago
webczat

Mapping json objects with known schema but unknown number of properties

hello. in stj, I have to map something, as in discord error structures. Main top level object is a record with message, code and optional "Errors" property, which I have tried to map like so (unsuccessfully):
public sealed record ErrorDetailsModel
{
[JsonPropertyName("_errors")]
public Optional<List<ErrorModel>> Errors { get; init; }

[JsonExtensionData]
public Dictionary<string, ErrorDetailsModel> Items { get; init; } = new();
}
public sealed record ErrorDetailsModel
{
[JsonPropertyName("_errors")]
public Optional<List<ErrorModel>> Errors { get; init; }

[JsonExtensionData]
public Dictionary<string, ErrorDetailsModel> Items { get; init; } = new();
}
Of course the problem is that JsonExtensionData does not accept that form and the dictionary value must be JsonElement or object, where object actually also means JsonElement. How to handle that situation so that I don't have to parse JsonElement or so that I can hide it, if I know this structure is just recursive?
11 Replies
ero
ero•3w ago
"I have to map something" is a bit vague. what does the json look like? why does this form not work? have you considered a converter?
canton7
canton7•3w ago
I'm going to take a stab in the dark and say that the Optional there isn't doing you any favours? But yes, you'll need to post your actual JSON, and what exactly is going wrong
webczat
webczatOP•3w ago
the optional is actually required. I mean, not in this specific case to be honest, but in other models in the api, i need to be able to distinguish between null and optional but not present. so all non optional fields are marked required.
canton7
canton7•3w ago
But, does STJ know how to deal with your Optional type?
webczat
webczatOP•3w ago
this is like:
{
"_errors": [...]
"anyprop": value
}
{
"_errors": [...]
"anyprop": value
}
where value is another instance of this very same object. I've taught it.
canton7
canton7•3w ago
That would be really useful information to share in your question 😛
webczat
webczatOP•3w ago
nah. because the existence and presence of Optional is irrelevant to this question. the only important part is the extensiondata. might introduce an OptionalAttribute btw so that I can mark fields that might be skipped but for which it's not actually beneficial to be able to discover whether they're not present... as in, you're right that in this specific case I'd prefer to just receive an empty list. but it's unrelated to the question itself
canton7
canton7•3w ago
Erm, yes, because otherwise people like me spot it as a likely reason your code isn't working 😉
webczat
webczatOP•3w ago
hahah... I mean good catch. I need this Optional, but certainly not here. I might even theoretically make collections exempt from the "everything is required" but this will probably introduce confusion what isn't working is what I mentioned. I received a code analyzer warning about the Errors property which is marked JsonExtensionData. and I know why the property is not working, however it's currently not changed and current shape of record shows how I would like this property to behave
canton7
canton7•3w ago
Looks like you'll either need to use JsonElement and deserialise that to ErrorModel manually, or write your own converter
webczat
webczatOP•3w ago
the thing is I'd kinda prefer to make this self contained. and I'm not sure if I want to make a converter. like, would be happy to make the JsonElement deserialization logic to stay inside the record so that nobody in upper layyers is forced to deal with it. my problem is that the record never receives JsonSerializerOptions that I need to pass to Deserialize. as in would prefer my behavior to be additive instead of replacing the whole mapping logic for that type IOnJsonDeserialized would be ideal except it just doesn't accept any parameters in OnDeserialized the only thing I could do to use a similar pattern is to modify the contract and make either something called or some magic property/interface to be used to set JsonSerializerOptions before OnDeserialized but maybe you have better ideas oh... i actually have another weird idea, i can add my own extensiondata prop to the contract and define my own logic for it. sometimes my brain is just slow

Did you find this page helpful?