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

AVRO-4028: Avro.AvroException: Unable to find type 'IDictionary<string, Foo>' in all loaded assemblies #3072

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 117 additions & 69 deletions lang/csharp/src/apache/main/Specific/ObjectCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,15 @@
public static ObjectCreator Instance { get; } = new ObjectCreator();

/// <summary>
/// Static generic dictionary type used for creating new dictionary instances
/// Static generic dictionary type used for creating new Dictionary instances
/// </summary>
private readonly Type GenericMapType = typeof(Dictionary<,>);

/// <summary>
/// Static generic dictionary type used for creating new IDictionary instances
/// </summary>
private readonly Type GenericIMapType = typeof(IDictionary<,>);

/// <summary>
/// Static generic list type used for creating new array instances
/// </summary>
Expand Down Expand Up @@ -90,6 +95,14 @@
return GenericIListType.MakeGenericType(FindType(itemTypeName));
}

if (TryGetIDictionaryItemTypeName(name, out var itemTypesName))
{
var key = itemTypesName[0].GetType().Name;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic on the line 135 (if (name == t.Name || name == t.FullName ..) compare in case sensetive way. So it's not able to handle string (system name is String)

.GetType().Name helps to handle string type correctly (changing string to String).

That is not a flexible solution and it requires key to always be a string. Changing the type names comparing logic also does not sound like a good idea.

var value = itemTypesName[1];

return GenericIMapType.MakeGenericType(FindType(key), FindType(value));
}

if (TryGetNullableItemTypeName(name, out itemTypeName))
{
return GenericNullableType.MakeGenericType(FindType(itemTypeName));
Expand Down Expand Up @@ -168,6 +181,40 @@
return false;
}

private bool TryGetIDictionaryItemTypeName(string name, out string[] itemTypesName)
{
const string dictionaryPrefix = "IDictionary<";
const string fullDictionaryPrefix = "System.Collections.Generic.IDictionary<";
string[] separators = { ", ", "," };

if (!name.EndsWith(">", StringComparison.Ordinal))
{
itemTypesName = null;
return false;
}

if (name.StartsWith(fullDictionaryPrefix, StringComparison.Ordinal))
{
itemTypesName = name
.Substring(dictionaryPrefix.Length, name.Length - dictionaryPrefix.Length - 1)
.Split(separators, 2, StringSplitOptions.None);

return true;
}

if (name.StartsWith(dictionaryPrefix, StringComparison.Ordinal))
{
itemTypesName = name
.Substring(dictionaryPrefix.Length, name.Length - dictionaryPrefix.Length - 1)
.Split(separators, 2, StringSplitOptions.None);

return true;
}

itemTypesName = null;
return false;
}

private bool TryGetNullableItemTypeName(string name, out string itemTypeName)
{
const string nullablePrefix = "Nullable<";
Expand Down Expand Up @@ -201,87 +248,88 @@
/// </exception>
public Type GetType(Schema schema)
{
switch(schema.Tag) {
case Schema.Type.Null:
break;
case Schema.Type.Boolean:
return typeof(bool);
case Schema.Type.Int:
return typeof(int);
case Schema.Type.Long:
return typeof(long);
case Schema.Type.Float:
return typeof(float);
case Schema.Type.Double:
return typeof(double);
case Schema.Type.Bytes:
return typeof(byte[]);
case Schema.Type.String:
return typeof(string);
case Schema.Type.Union:
{
if (schema is UnionSchema unSchema && unSchema.Count == 2)
switch (schema.Tag)
{
case Schema.Type.Null:
break;
case Schema.Type.Boolean:
return typeof(bool);
case Schema.Type.Int:
return typeof(int);
case Schema.Type.Long:
return typeof(long);
case Schema.Type.Float:
return typeof(float);
case Schema.Type.Double:
return typeof(double);
case Schema.Type.Bytes:
return typeof(byte[]);
case Schema.Type.String:
return typeof(string);
case Schema.Type.Union:
{
Schema s1 = unSchema.Schemas[0];
Schema s2 = unSchema.Schemas[1];

// Nullable ?
Type itemType = null;
if (s1.Tag == Schema.Type.Null)
if (schema is UnionSchema unSchema && unSchema.Count == 2)
{
itemType = GetType(s2);
}
else if (s2.Tag == Schema.Type.Null)
{
itemType = GetType(s1);
}
Schema s1 = unSchema.Schemas[0];
Schema s2 = unSchema.Schemas[1];

if (itemType != null)
{
if (itemType.IsValueType && !itemType.IsEnum)
// Nullable ?
Type itemType = null;
if (s1.Tag == Schema.Type.Null)
{
try
{
return GenericNullableType.MakeGenericType(itemType);
}
catch
itemType = GetType(s2);
}
else if (s2.Tag == Schema.Type.Null)
{
itemType = GetType(s1);
}

if (itemType != null)
{
if (itemType.IsValueType && !itemType.IsEnum)
{
try
{
return GenericNullableType.MakeGenericType(itemType);
}
catch
{
}

Check notice

Code scanning / CodeQL

Poor error handling: empty catch block Note

Poor error handling: empty catch block.

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
}
}

return itemType;
return itemType;
}
}
}

return typeof(object);
}
case Schema.Type.Array:
{
ArraySchema arrSchema = schema as ArraySchema;
Type itemSchema = GetType(arrSchema.ItemSchema);

return GenericListType.MakeGenericType(itemSchema);
}
case Schema.Type.Map:
{
MapSchema mapSchema = schema as MapSchema;
Type itemSchema = GetType(mapSchema.ValueSchema);
return typeof(object);
}
case Schema.Type.Array:
{
ArraySchema arrSchema = schema as ArraySchema;
Type itemSchema = GetType(arrSchema.ItemSchema);

Check warning

Code scanning / CodeQL

Dereferenced variable may be null Warning

Variable
arrSchema
may be null at this access because of
this
assignment.

return GenericMapType.MakeGenericType(typeof(string), itemSchema );
}
case Schema.Type.Enumeration:
case Schema.Type.Record:
case Schema.Type.Fixed:
case Schema.Type.Error:
{
// Should all be named types
if (schema is NamedSchema named)
return GenericListType.MakeGenericType(itemSchema);
}
case Schema.Type.Map:
{
return FindType(named.Fullname);
MapSchema mapSchema = schema as MapSchema;
Type itemSchema = GetType(mapSchema.ValueSchema);

Check warning

Code scanning / CodeQL

Dereferenced variable may be null Warning

Variable
mapSchema
may be null at this access because of
this
assignment.

return GenericMapType.MakeGenericType(typeof(string), itemSchema);
}
case Schema.Type.Enumeration:
case Schema.Type.Record:
case Schema.Type.Fixed:
case Schema.Type.Error:
{
// Should all be named types
if (schema is NamedSchema named)
{
return FindType(named.Fullname);
}

break;
}
break;
}
}

// Fallback
Expand Down
3 changes: 3 additions & 0 deletions lang/csharp/src/apache/test/Specific/ObjectCreatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ public void TestGetTypeEquals(string name, Schema.Type schemaType, Type expected
[TestCase("IList<IList<IList<Foo>>>", Schema.Type.Array, typeof(IList<IList<IList<IList<Foo>>>>))]
[TestCase("System.Collections.Generic.IList<System.Collections.Generic.IList<System.Collections.Generic.IList<Foo>>>", Schema.Type.Array, typeof(IList<IList<IList<IList<Foo>>>>))]
[TestCase("Foo", Schema.Type.Map, typeof(IDictionary<string, Foo>))]
[TestCase("IDictionary<string, Foo>", Schema.Type.Map, typeof(IDictionary<string, IDictionary<string, Foo>>))]
[TestCase("IDictionary<string, IDictionary<string, IDictionary<string, Foo>>>", Schema.Type.Map, typeof(IDictionary<string, IDictionary<string, IDictionary<string, IDictionary<string, Foo>>>>))]
[TestCase("System.Collections.Generic.IDictionary<string, System.Collections.Generic.IDictionary<string, System.Collections.Generic.IDictionary<string, Foo>>>", Schema.Type.Map, typeof(IDictionary<string, IDictionary<string, IDictionary<string, IDictionary<string, Foo>>>>))]
[TestCase("Nullable<Int32>", Schema.Type.Array, typeof(IList<Nullable<int>>))]
[TestCase("System.Nullable<Int32>", Schema.Type.Array, typeof(IList<int?>))]
[TestCase("IList<Nullable<Int32>>", Schema.Type.Array, typeof(IList<IList<int?>>))]
Expand Down