-
-
Notifications
You must be signed in to change notification settings - Fork 482
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dynamic type resolution during deserialization based on serialized content #605
Comments
@nitz, If I've understood you correctly, I've got a working solution to this using this set of modifications: https://gist.github.com/atruskie/bfb7e9ee3df954a29cbc17bdf12405f9 I can determine the concrete deserialization type based on the presence of a key/field name, or the value of field. Original issue: #343 |
Woah, this... looks like it might work exactly like what I'm aiming for! Might be a bit wordier than I'd hope for, but is far and away cleaner than "converting to json, then parsing it with that" 😅 Also, I felt like I looked through the issues pretty good and didn't notice that one. Guess I glazed over when I read k8s. I will give this a shot today, but I'm betting based on your explanation and how that thread sounds that this should be right where I want to be. |
And in usual me fashion, somehow 'later today' becomes 3 days later. 🙃 First, I wanted to say thanks @atruskie for not only writing this a while back, but for taking the time to come share it with me. Anyways, I got a chance to implement this for the most part in my project, trying to genericize it as I did. Would be better for my use case where the parser will be told at runtime about the types it needs to process, and I was thinking if it ends up doing what I like then to clean it up and submit it as a PR or the sort. (An aside: you didn't explicitly license the code in your gist, I wanted to make sure to ask you about that!) I was able to get it working with cases like you demonstrated in your example easily, which is really 90% of the lifting I needed! The one roadbump I'm hitting now is a bit of what would be an edge case for the way it's written. I started to "dumb fix" it before I realized I should take a step back and come ask for opinions about the best way to approach this edge case. The case I'm hitting is essentially a multi-document YAML file with 0 to N amounts of nodes that would be best described as the So once I got that worked out I'll circle back around to this bit. Super excited to see if I can get this going :) Thanks again! |
@nitz updated gist with MIT license. You'll need some sort of discriminator to choose the type. Avoiding the specific names I used for my examples (that's confusing), can you identify what your discriminator is?
I don't think the multi-document aspect of this should matter. Working off of your example you'd want something like the following: public class FooTypeResolver : ITypeDiscriminator
{
private readonly Dictionary<string, Type> typeLookup;
public FooTypeResolver(INamingConvention namingConvention)
{
typeLookup = new Dictionary<string, Type>() {
{ "url", typeof(UrlFoo) },
{ "ftp" , typeof(FtpFoo) },
// more here...
};
}
public Type BaseType => typeof(FooBase);
public bool TryResolve(ParsingEventBuffer buffer, out Type suggestedType)
{
if (buffer.TryFindMappingEntry(
scalar => typeLookup.ContainsKey(scalar.Value),
out Scalar key,
out ParsingEvent _))
{
suggestedType = typeLookup[key.Value];
return true;
}
suggestedType = null;
return false;
}
} I'm not sure what the If we're to continue this discussion further, it might be good to do so in the gist comments, so we don't fill this repo with discussion unrelated to code that is not currently in the repo. |
Yeah I think it's because I'm trying to call deserialize with |
Since we added a feature a while back for this exact use case I’m closing this issue. |
Is your feature request related to a problem? Please describe.
This one is a bit of a doozy, and touches on several issues I've found are already open or related. To tag them quickly: The workflow I'm looking to support is related somewhat to the feature added in #601, and partially described or would be affected by the topics in #408, #459, and maybe tangent related to #529 and #593.
Before I get started here as well: I am perfectly ready to be told "you dummy, it's super easy and you're just missing it!" So please do point out if I'm just glazing over features I shouldn't have!
Essentially, I've got some YAML I don't control. Nodes are untagged, but differ slightly in what they may contain. Other applications using this YAML react to each node by handling the node as a specific type based on the appearance of specific keys in direct children of the node. This is my goal as well.
With (my understanding) of the numerous modifications I can make to the parse/deserialization process, it seems I'm very close to being able to react the way I'd like, but each method I've tried to solve it has had some various shortcomings ranging from not able to have enough information to construct the proper type, to probably being able to handle it but am left parsing out the entire object on my own. I've got a more concrete example in the section below.
Example problem
I apologize if this gets long winded or too verbose, just want to try and paint a complete picture. Please excuse any syntax errors, as I'm just typing this up all freeform! 🙂
I'll start with two example documents, and the data classes used to represent them.
First, my list of foos.
Second, my foo settings (and other settings) file.
And now, the .NET data model:
Then, using that model to deserialize the settings:
And also using that model to deserialize the list of various foos:
As it stands, each of the
With{...}
methods I've tried to use to augment the deserializing behavior falls short in the following ways:TypeResolver
— I thought at first this was the right path, but docs are a little scarse at the moment. Perhaps I'm just misreading or misusing it, but my custom type resolver never gets invoked.NodeTypeResolver
— This, I believe, is the closest to what I desire after seeing thatTypeResolver
probably wasn't what I wanted. TheResolve()
method comes with the node event and the type it currently wants. So when I see something come in as anIFoo
, then I can react and provide the proper type. The problem, however, is that the node event is theMappingStart
(which makes sense, as that's when the object would be created...), but I can't investigate any details about the child nodes, so I can't provide the properType
here.ObjectFactory
— This made the second most sense to me, as it's requested to create the typeIFoo
, so I can handle that and proxy everything else to a fallback factory, like the doc examples do. It's not quite as elegant as providing the type in theNodeTypeResolver
, because this all but guarantees I can't have my objects be immutable. As well, it suffers from the same issue theNodeTypeResolver
does, in that the only information provided to the factory about the object the deserializer wishes to create is the type itself. I once again can't inspect child nodes to react properly.TypeInspector
— I may be missing something on this one with brief or unfinished docs, but reading the code, this one seems like it's not designed to solve what I'm aiming for and is more for matching up the actual YAML to the named properties.TypeInspector
does seem like the proper route to use in order to map to get/init properties so that I can still assign them after object creaton.NodeDeserializer
andIYamlTypeConverter
— Both of these seem very related in what they accomplish, with just slight differences in API.NodeDeserializer
felt the most capable as it came along with anestedObjectDeserializer
, as noted in TypeConverters should be able to deserialize complex types #459 (IYamlTypeConverter
seems to fall short there.) As well, I implemented myNodeDeserializer
as in the docs, bringing along an ObjectSerializer to pass on node types I was uninterested in handling. So now I'm in a place where I'm able to actually see the data, and know when I want to create anIFoo
! The drawback now is I have to manually parse myIFoo
and everything underneath it. What's made more difficult to manage is: I can't actually create the proper object type until I start parsing down, potentially going past 1 or more children nodes that I'm not able to use to make a determination of the proper type I need. So now I'm left juggling parsed or partially parsed events until I can create the proper type, and then I have to fill in the object myself. (Unless I'm missing how to use thenestedObjectDeserializer
or my inner deserializer to leverage the rest of the deserialization process but while providing the proper object type.)Okay, that was a lot of text. I tried to clean it up and make as much sense as I could, but it's still a mess. Please don't feel bad asking for clarification of my balderdash.
Describe the solution you'd like
I'd like a way, (probably through the
INodeTypeResolver
orIObjectFactory
mechanisms, but where is not incredibly important) to be able to dynamically create a concrete .NET object that will populate an interface or base class in the resulting deserialized object. Ideally, allowing automatic parsing of the object (and it's children) after creating it's concrete instance.Describe alternatives you've considered
As I'm also consuming JSON,
Json.NET
has a mechanism that allows for this fairly easy. Because theJson.NET
parser can represent the current deserialization state as a completely tokenized object graph through it's JObject (et. al.) class, I'm able to solve my problem via a small JsonConverter. The reason it's able to work in this way, is because the converter is provided the entire node (and the entire hierarchy from that point downward,) allowing the typical deserialization consumption functions to be leveraged.Assuming my data structures are similar to the example from above, my json converter looks something like this:
Since this method works, my current (yikes) workaround is converting to JSON, and then deserializing using it. 😬
Additional context
Okay, so that was a lot. But no one has ever said I don't attempt to be thorough.
I appreciate any thoughts or feedback, and am also very welcome to being shown the right way to do things on the (very likely) chance I missed it.
Thanks for reading!
The text was updated successfully, but these errors were encountered: