Skip to content
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

question: how to instantiate a different type during serializing (or, is IDictionary<k,v> bugged?) #463

Closed
binary1230 opened this issue Oct 7, 2020 · 9 comments
Assignees
Labels
enhancement (cc: feat) New feature or request question Further information is requested

Comments

@binary1230
Copy link

binary1230 commented Oct 7, 2020

First: HI! This library is amazing. Thank you for working on it.

I -think- I have a likely a documentation/understanding question, but, there's a small chance this might be a bug.

If I use a regular Dictionary<somekey,somevalue> class, serialization works great.

ISSUE:
When I tried to use a third-party dictionary class, I was seeing exceptions with an invalid attempt to cast to IDictionary

Here is a test reproducing the issue with a type called DictionaryExtended that inherits from IDictionary<TKey, TValue>

using System.Collections;
using System.Collections.Generic;
using ExtendedXmlSerializer.Configuration;
using ExtendedXmlSerializer.Tests.ReportedIssues.Support;
using Xunit;

namespace ExtendedXmlSerializer.Tests.ReportedIssues
{
	public sealed class DictionarySerializerNewIssue
	{
		[Fact]
		public void Verify()
		{
			var serializer = new ConfigurationContainer()
				.Create().ForTesting();

                        // my custom IDictionary<k,v> based type
			var testInstance = new DictionaryExtended {
				[0] = "Test0",
				[1] = "Test1",
				[2] = "Test2"
			};
			
			// this line throws
			serializer.Serialize(testInstance);
			// TODO    .Should().Be(@"some-xml");
		}

		[Fact]
		public void Configuration()
		{
			var serializer = new ConfigurationContainer()
				.Create()
				.ForTesting();
		}

		// Custom simple dictionary class.  The key thing seems to be: 
                // this class inherits from IDictionary<int,string> 
                // BUT it does NOT inherit from the non-generic IDictionary (the one with no types attached)
                //
                // this class was created minimally by deriving from IDictionary<int,string>
		// right click -> implement missing members by delegating to new object (dictionary).
		// it creates a bunch of members to implement to the interface
		// then add the initializer for the _dict
		public class DictionaryExtended : IDictionary<int, string>
		{
			IDictionary<int, string> _dict = new Dictionary<int, string>();

			public IEnumerator<KeyValuePair<int, string>> GetEnumerator()
			{
				return _dict.GetEnumerator();
			}

			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable) _dict).GetEnumerator();
			}

			public void Add(KeyValuePair<int, string> item)
			{
				_dict.Add(item);
			}

			public void Clear()
			{
				_dict.Clear();
			}

			public bool Contains(KeyValuePair<int, string> item)
			{
				return _dict.Contains(item);
			}

			public void CopyTo(KeyValuePair<int, string>[] array, int arrayIndex)
			{
				_dict.CopyTo(array, arrayIndex);
			}

			public bool Remove(KeyValuePair<int, string> item)
			{
				return _dict.Remove(item);
			}

			public int Count => _dict.Count;

			public bool IsReadOnly => _dict.IsReadOnly;

			public bool ContainsKey(int key)
			{
				return _dict.ContainsKey(key);
			}

			public void Add(int key, string value)
			{
				_dict.Add(key, value);
			}

			public bool Remove(int key)
			{
				return _dict.Remove(key);
			}

			public bool TryGetValue(int key, out string value)
			{
				return _dict.TryGetValue(key, out value);
			}

			public string this[int key]
			{
				get => _dict[key];
				set => _dict[key] = value;
			}

			public ICollection<int> Keys => _dict.Keys;

			public ICollection<string> Values => _dict.Values;
		}
	}
}

I expect that to work, but, I see the following exception when I run it:

ExtendedXmlSerializer.Tests.ReportedIssues.DictionarySerializerNewIssue.Verify

System.InvalidCastException: Unable to cast object of type 'DictionaryExtended' to type 'System.Collections.IDictionary'.

System.InvalidCastException
Unable to cast object of type 'DictionaryExtended' to type 'System.Collections.IDictionary'.
   at ExtendedXmlSerializer.Tests.ReportedIssues.DictionarySerializerNewIssue.Verify() in D:\projects\extendedxmlserializer\test\ExtendedXmlSerializer.Tests.ReportedIssues\DictSerializeIssue.cs:line 25

ExtendedXmlSerializer is rightfully complaining, this type can't be cast to IDictionary. For instance, if I run the same cast manually, it fails:

(IDictionary)testInstance;

That cast is happening inside ExtendedXmlSerializer at DictionaryEnumerators.cs:17

public IEnumerator Get(IEnumerable parameter) => (IEnumerator ((IDictionary)parameter)?.GetEnumerator() ?? _entries.GetEnumerator();

the key part there that fails is when it tries this cast:

(IDictionary)parameter

full stack trace below (this is from a slightly different project but same result)

System.InvalidCastException
   at ExtendedXmlSerializer.ReflectionModel.DictionaryEnumerators.Get(IEnumerable parameter) in C:\projects\extendedxmlserializer\src\ExtendedXmlSerializer\ReflectionModel\DictionaryEnumerators.cs:line 17
   at ExtendedXmlSerializer.ContentModel.Members.InstanceMemberWalkerBase`1.<Enumerate>d__5.MoveNext()
   at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
   at System.Linq.Enumerable.<SelectManyIterator>d__17`2.MoveNext()
   at System.Linq.Enumerable.<ConcatIterator>d__59`1.MoveNext()
   at System.Linq.Enumerable.<SelectManyIterator>d__17`2.MoveNext()
   at System.Linq.Enumerable.<DistinctIterator>d__64`1.MoveNext()
   at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
   at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
   at System.Collections.Immutable.ImmutableArray.CreateRange[T](IEnumerable`1 items)
   at ExtendedXmlSerializer.ExtensionModel.References.ReferenceAwareSerializers.Serializer.Write(IFormatWriter writer, Object instance) in C:\projects\extendedxmlserializer\src\ExtendedXmlSerializer\ExtensionModel\References\ReferenceAwareSerializers.cs:line 60
   at ExtendedXmlSerializer.ExtensionModel.Xml.Write.Execute(Writing parameter) in C:\projects\extendedxmlserializer\src\ExtendedXmlSerializer\ExtensionModel\Xml\Write.cs:line 21
   at ExtendedXmlSerializer.ExtensionModel.Xml.Serializer.Serialize(XmlWriter writer, Object instance) in C:\projects\extendedxmlserializer\src\ExtendedXmlSerializer\ExtensionModel\Xml\Serializer.cs:line 19
   at ExtendedXmlSerializer.ExtensionModel.Xml.InstanceFormatter.Get(Object parameter) in C:\projects\extendedxmlserializer\src\ExtendedXmlSerializer\ExtensionModel\Xml\InstanceFormatter.cs:line 33
   at ExtendedXmlSerializer.ExtensionMethodsForSerialization.Serialize(IExtendedXmlSerializer this, XmlWriterSettings settings, Object instance) in C:\projects\extendedxmlserializer\src\ExtendedXmlSerializer\ExtensionMethodsForSerialization.cs:line 43
   at Diz.Test.SerializerTest.Serializer() in D:\projects\src\diztinguish\Diz.Test\SerializerTest.cs:line 66

If I change DictionaryExtended to ALSO inherit from IDictionary, I get a different error about converting keys and values (would need to track it down further to reproduce).


I know I can't directly cast from IDictionary to IDictionary<TKey, TValue>. I'm looking for advice on best way to work around this in ExtendedXmlSerializer. It feels like not being able to serialize an IDictionary<k,v> derived type is a bug, but, I'm not sure. Or, maybe I messed up something with my enumerators, or maybe this is all a crazy approach. I just want serialized dictionaries :)

I suspect the new Interceptor approach in #451 is a good way to solve this, but, I couldn't get it working. That seems like the right way to say 'hey, anytime you think you should cast to IDictionary for an object of type DictionaryExtended, stop, and instead call my code so I can just create a DictionaryExtended for you, then pick it up from there'.

Either that or, could this be a weird interaction with the serializer not handling the generic params correctly? i.e. it's trying to create and cast to IDictionary when it should instead be casting to IDictionary<int,string>

And perhaps it's an issue with ExtendedXmlSerializer thinking it's OK to cast DictionaryExtended to IDictionary when in fact it's not actually derived form that interface (just a confusingly similarly named IDictionary<TKey,TValue>)?


Unrelated question [sorry], is there a way to use .TypeOf<>() to select all generic classes?
i.e. say I have that same DictionaryExtended<K,V> class and it has a member named Test i'd like to ignore.

Right now it seems I have to type:
.TypeOf<DictionaryExtended<int,string>>().Member(x => x.Test).Ignore()
i.e. I have to specify every combo of generic params (say, <int,string>,<int,int>,<SomeType,SomeOtherType> etc).

Did I miss a way to just say something like:
.TypeNameMatches("DictionaryExtended").Member(x => x.Test).Ignore()

Thanks so much for your time, I know it's tight right now from your other issue. \m/

@issue-label-bot issue-label-bot bot added the question Further information is requested label Oct 7, 2020
@issue-label-bot
Copy link

Issue-Label Bot is automatically applying the label question to this issue, with a confidence of 0.80. Please mark this comment with 👍 or 👎 to give our bot feedback!

Links: app homepage, dashboard and code for this bot.

@binary1230
Copy link
Author

👍 issue-label-bot. you done good.

@Mike-E-angelo
Copy link
Member

Hey @binary1230 thank you for writing in with the kind words and for all the great detail! From the outset I would say we are indeed running into a limitation of how ExtendedXmlSerializer views dictionaries. I will look into this, however, and see if there is an easy fix for this (or perhaps a workaround with an extension).

As for your question on querying generic types, you have for sure run into a limitation. 😁 Right now our querying constructs only allow for direct type definitions. Adding such functionality would be a challenge, but if someone would like to embark on the path of providing a PR that allows for such capability I would not stop them. :) 👍

@Mike-E-angelo Mike-E-angelo self-assigned this Oct 10, 2020
@create-issue-branch
Copy link

Branch issues/other/i463 created!

@binary1230
Copy link
Author

binary1230 commented Oct 10, 2020

Cool! thanks for looking at it, I really appreciate it.

The project I'm using this for is over here, I actually did end up figuring out how to build a self-registering system for generic types though.... it's probably too hacky for real use. However, it's a neat proof of concept: /~https://github.com/binary1230/DiztinGUIsh/blob/master/Diz.Core/util/ObservableDictionaryAdaptor.cs#L18

The key being I use reflection to go through the loaded assemblies, find all the combo of generic types that I want to serialize, and then create a list of functions to apply with ConfigurationContainer. So, effectively, at the start of serialization, I call this on each generic type automatically:

(here, OdWrapper<TKey,Tvalue> is my custom type that I want to apply .Member().Ignore to every instance of it)

        public static IConfigurationContainer AppendDisablingType<TKey, TValue>(this IConfigurationContainer @this)
            => @this
                .EnableImplicitTyping(typeof(OdWrapper<TKey, TValue>))
                .Type<OdWrapper<TKey, TValue>>()
                .Member(x => x.Dict).Ignore();

@Mike-E-angelo
Copy link
Member

Wow! Very cool @binary1230. It's a little humbling to see how others are using this library and how they are integrating with it. Hopefully someone will be able to utilize your work in their own way as well. I think it would be cool to create a library at some point, maybe ExtendedXmlSerializer.Extensions with useful contributions like this. It would have to be totally community-driven, however. I am tapped out ATM. 😅

Speaking of which. 😁 I have a build for you to try out here:

#465 (comment)

Please let me know how that treats you and if everything looks good I will put it into the build slated for Tuesday. 🤞

@Mike-E-angelo Mike-E-angelo added the enhancement (cc: feat) New feature or request label Oct 10, 2020
@binary1230
Copy link
Author

YESSSS. that does the trick really nicely. thank you so much!

I've integrated it into IsoFrieze/DiztinGUIsh#18 and was able to rip out the wrapper stuff. it all seems to be working great!

Thanks again!

@Mike-E-angelo
Copy link
Member

Cool! Very glad to hear that, @binary1230. I will get this merged and then pushed out to NuGet this Tuesday (our usual deployment time). I will update here when it is ready.

@Mike-E-angelo
Copy link
Member

Mike-E-angelo commented Oct 13, 2020

This is now available in NuGet:

https://www.nuget.org/packages/ExtendedXmlSerializer/

Please do let me know of any issues you encounter and I will take a look into it for you. 👍 Closing for now.

An extensible Xml Serializer for .NET that builds on the functionality of the classic XmlSerializer with a powerful and robust extension model.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement (cc: feat) New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants