C
C#13mo ago
SWEETPONY

❔ How to remove extra fields during reflection?

I have following method:
private IDictionary<string, OpenApiSchema> GenerateProperties(Type arguments)
{
var properties = new Dictionary<string, OpenApiSchema>();
var processedTypes = new HashSet<Type>(); // Maintain a set of processed types
GeneratePropertiesRecursive(arguments, properties, processedTypes);

return properties;
}
private IDictionary<string, OpenApiSchema> GenerateProperties(Type arguments)
{
var properties = new Dictionary<string, OpenApiSchema>();
var processedTypes = new HashSet<Type>(); // Maintain a set of processed types
GeneratePropertiesRecursive(arguments, properties, processedTypes);

return properties;
}
private void GeneratePropertiesRecursive( Type type, IDictionary<string, OpenApiSchema> properties, HashSet<Type> processedTypes )
{
var propertyInfos = type.GetProperties();
foreach ( var propertyInfo in propertyInfos )
{
var propertyName = propertyInfo.Name;
var propertyType = propertyInfo.PropertyType;

var propertySchema = new OpenApiSchema();

if ( processedTypes.Contains( propertyType ) )
{
// Skip processing if property type has already been processed
// This prevents infinite recursion in case of circular references
continue;
}

processedTypes.Add( propertyType );

if ( propertyType.IsClass )
{
GeneratePropertiesRecursive( propertyType, propertySchema.Properties, processedTypes );
}

if ( propertyType == typeof( int ) )
{
propertySchema.Type = "integer";
propertySchema.Format = "int64";
}
else if ( propertyType == typeof( string ) )
{
propertySchema.Type = "string";
}
else if ( propertyType == typeof( bool ) )
{
propertySchema.Type = "boolean";
}

properties.Add( propertyName, propertySchema );
}
}
private void GeneratePropertiesRecursive( Type type, IDictionary<string, OpenApiSchema> properties, HashSet<Type> processedTypes )
{
var propertyInfos = type.GetProperties();
foreach ( var propertyInfo in propertyInfos )
{
var propertyName = propertyInfo.Name;
var propertyType = propertyInfo.PropertyType;

var propertySchema = new OpenApiSchema();

if ( processedTypes.Contains( propertyType ) )
{
// Skip processing if property type has already been processed
// This prevents infinite recursion in case of circular references
continue;
}

processedTypes.Add( propertyType );

if ( propertyType.IsClass )
{
GeneratePropertiesRecursive( propertyType, propertySchema.Properties, processedTypes );
}

if ( propertyType == typeof( int ) )
{
propertySchema.Type = "integer";
propertySchema.Format = "int64";
}
else if ( propertyType == typeof( string ) )
{
propertySchema.Type = "string";
}
else if ( propertyType == typeof( bool ) )
{
propertySchema.Type = "boolean";
}

properties.Add( propertyName, propertySchema );
}
}
the main problem: I want to process the following class:
public class ReportsGenerateArguments
: EntityItems<ReportGenerateDto>{}
public class ReportsGenerateArguments
: EntityItems<ReportGenerateDto>{}
public class ReportGenerateDto
{
[JsonProperty( PropertyName = "parameters" )]
public JObject Parameters { get; set; }}
public class ReportGenerateDto
{
[JsonProperty( PropertyName = "parameters" )]
public JObject Parameters { get; set; }}
and get the result: items: "parameters"
34 Replies
SWEETPONY
SWEETPONY13mo ago
but got:
Parameters:
properties:
Type: { }
Item:
properties:
EqualityComparer: { }
Parent: { }
Parameters:
properties:
Type: { }
Item:
properties:
EqualityComparer: { }
Parent: { }
ero
ero13mo ago
well sure JObject contains the properties EqualityComparer and Parent seems expected to me?
JakenVeina
JakenVeina13mo ago
yeah, like.... you wrote code to recursively walk properties but you expect the result to be non-recursive? what are we missing here?
ero
ero13mo ago
(also the Contains check can be simplified)
if ( !processedTypes.Add( propertyType ) )
{
//
}
if ( !processedTypes.Add( propertyType ) )
{
//
}
SWEETPONY
SWEETPONY13mo ago
I will explain what do I want to get I have this class:
public class ReportsGenerateArguments
: EntityItems<ReportGenerateDto>
{
}
public class ReportsGenerateArguments
: EntityItems<ReportGenerateDto>
{
}
public class EntityItems<TEntity>
{
public EntityItems()
{
}

public EntityItems(TEntity item)
{
Items = new() { item };
}

public EntityItems(IEnumerable<TEntity> items)
{
Items = new( items );
}

[JsonProperty(PropertyName = "items")]
public List<TEntity> Items { get; set; } =
new();
}
public class EntityItems<TEntity>
{
public EntityItems()
{
}

public EntityItems(TEntity item)
{
Items = new() { item };
}

public EntityItems(IEnumerable<TEntity> items)
{
Items = new( items );
}

[JsonProperty(PropertyName = "items")]
public List<TEntity> Items { get; set; } =
new();
}
public class ReportGenerateDto
{
[JsonProperty(PropertyName = "template_name")]
[JsonRequired]
public string TemplateName { get; set; }

[JsonProperty(PropertyName = "output_parameters")]
[JsonRequired]
public ReportOutputParametersDto OutputParameters { get; set; } =
new();

[JsonProperty(PropertyName = "parameters")]
public JObject Parameters { get; set; }
}
public class ReportGenerateDto
{
[JsonProperty(PropertyName = "template_name")]
[JsonRequired]
public string TemplateName { get; set; }

[JsonProperty(PropertyName = "output_parameters")]
[JsonRequired]
public ReportOutputParametersDto OutputParameters { get; set; } =
new();

[JsonProperty(PropertyName = "parameters")]
public JObject Parameters { get; set; }
}
ReportOutputParametersDto is a class with properties so method should be recursive and this how filled object looks like
"items": [
{
"template_name": "visitor_control_by_visits",
"parameters": {
"has_visits": "true",
"filter": {}
},
"output_parameters": {
"is_required_auth": "false",
"categories": ["visitor_control"]
}
}
]
"items": [
{
"template_name": "visitor_control_by_visits",
"parameters": {
"has_visits": "true",
"filter": {}
},
"output_parameters": {
"is_required_auth": "false",
"categories": ["visitor_control"]
}
}
]
all I want is to represent this object as OpenApiSchema but I can't do anything my method return this: reports_generate_arguments: type: object properties: Items: properties: Capacity: type: integer format: int64 Item: properties: TemplateName: type: string properties: Chars: { } OutputParameters: properties: Ttl: { } IsRequiredHttps: type: boolean Format: { } Parameters: properties: Type: { } looks awful
JakenVeina
JakenVeina13mo ago
I find myself even more confused what is OpenApiSchema? what's wrong with the output you're getting? do you want it to be recursive or not?
SWEETPONY
SWEETPONY13mo ago
I just want to get all properties with JsonProperty attribute
SWEETPONY
SWEETPONY13mo ago
too much properties I don’t need Chars: {} for example
ero
ero13mo ago
This was never mentioned until now, and never even attempted in any code sent So it was kinda impossible to know this
SWEETPONY
SWEETPONY13mo ago
yes, I'm sorry I can show one thing
ero
ero13mo ago
But sure, just get the attributes on the property propertyInfo.GetCustomAttributes<JsonProperty>() And then check whether the return value is non-null
SWEETPONY
SWEETPONY13mo ago
yes, I can do this: if (propertyInfo.GetCustomAttribute<JsonPropertyAttribute>() == null) continue; but the problem is here:
reports_generate_arguments:
type: object
reports_generate_arguments:
type: object
I don't see any properties the generated result doesn't look like what I need and I don't even know why I get this... I just add:
foreach (var propertyInfo in propertyInfos)
{
if ( propertyInfo.GetCustomAttribute<JsonPropertyAttribute>() == null )
continue;
var propertyName = propertyInfo.Name;
var propertyType = propertyInfo.PropertyType;

var propertySchema = new OpenApiSchema();
...
}
foreach (var propertyInfo in propertyInfos)
{
if ( propertyInfo.GetCustomAttribute<JsonPropertyAttribute>() == null )
continue;
var propertyName = propertyInfo.Name;
var propertyType = propertyInfo.PropertyType;

var propertySchema = new OpenApiSchema();
...
}
SWEETPONY
SWEETPONY13mo ago
https://gist.github.com/INTERNALINTERFERENCE/06691907f3413b51a2afffff9626a955 full code, you can check it if you want to I'm too stupid to understand why it doesn't work as I want
Gist
.cs
GitHub Gist: instantly share code, notes, and snippets.
ero
ero13mo ago
Only thing I can think of is that the attributes aren't actually the same, or, of these are in different projects, the versions don't match
SWEETPONY
SWEETPONY13mo ago
hm I checked that, versions are match
SWEETPONY
SWEETPONY13mo ago
I think problem is here.. we just skip class
JakenVeina
JakenVeina13mo ago
nono, that's not the Items property that's the [] property although, yeah, I see the issue what you need is special logic to identify collections because your JSON engine has the same special logic List<T> doesn't have any properties with [JsonProperty] on it
SWEETPONY
SWEETPONY13mo ago
hm, it's correct
JakenVeina
JakenVeina13mo ago
the JSON engine identifes properties to be serialized as those that have [JsonProperty] OR those that are (probably) IEnumerable<T> so, that's what you need to do
SWEETPONY
SWEETPONY13mo ago
public class ReportsGenerateArguments
: EntityItems<ReportGenerateDto>
{
}
public class ReportsGenerateArguments
: EntityItems<ReportGenerateDto>
{
}
I thought that get properties will give me ReportGenerateDto which will be combined with EntityItems.HashSet oh.. sigh
JakenVeina
JakenVeina13mo ago
that's kinda what you have when you don't filter by [JsonProperty] but List<T> doesn't have that and you can't add it
SWEETPONY
SWEETPONY13mo ago
I'm sorry but I don't understand how to solve my problem.. what should I add to my code?
JakenVeina
JakenVeina13mo ago
you added code to skip properties that don't have [JsonProperty] you need to adjust that to not skip it if the property is an IEnumerable<T>
SWEETPONY
SWEETPONY13mo ago
ahh okay, thanks! I will try to do this
JakenVeina
JakenVeina13mo ago
when it IS an IEnumerable<T> you need to extract the T and then continue walking that type also emit whatever it is that represents an array in OpenApiSchema
SWEETPONY
SWEETPONY13mo ago
hm I don't think that I can extract the T maybe I should do smth like this:
public IEnumerable<Type> GetGenericIEnumerables(object o) {
return o.GetType()
.GetInterfaces()
.Where(t => t.IsGenericType == true
&& t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.Select(t => t.GetGenericArguments()[0]);
}
public IEnumerable<Type> GetGenericIEnumerables(object o) {
return o.GetType()
.GetInterfaces()
.Where(t => t.IsGenericType == true
&& t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.Select(t => t.GetGenericArguments()[0]);
}
what do you think?
JakenVeina
JakenVeina13mo ago
yes, that's what I mean this DOES allow for the possibility of a class that implements multiple IEnumerable<T>s but in practice, there shouldn't be anything like that in your model except maybe a Dictionary<TKey, TValue>? which is another type that is specially-handled by JSON engines
SWEETPONY
SWEETPONY13mo ago
thanks for helping me!
ero
ero13mo ago
it'll also be true for string but it's probably fine because they already check for that separately i would maybe just pass the type directly? you already have it in your recursive method after all
SWEETPONY
SWEETPONY13mo ago
but.. will it work? now I try this:
if (propertyType.IsClass)
{
if (IsEnumerableType(propertyType, out var genericType))
{
GeneratePropertiesRecursive(genericType, propertySchema.Properties, processedTypes);
}
else
{
GeneratePropertiesRecursive(propertyType, propertySchema.Properties, processedTypes);
} }

private bool IsEnumerableType(Type type, out Type elementType)
{
elementType = type.GetGenericArguments().FirstOrDefault();
return elementType != null && typeof(IEnumerable<>).MakeGenericType(elementType).IsAssignableFrom(type);
}
if (propertyType.IsClass)
{
if (IsEnumerableType(propertyType, out var genericType))
{
GeneratePropertiesRecursive(genericType, propertySchema.Properties, processedTypes);
}
else
{
GeneratePropertiesRecursive(propertyType, propertySchema.Properties, processedTypes);
} }

private bool IsEnumerableType(Type type, out Type elementType)
{
elementType = type.GetGenericArguments().FirstOrDefault();
return elementType != null && typeof(IEnumerable<>).MakeGenericType(elementType).IsAssignableFrom(type);
}
and it doesn't work I don't know what to come up with anymore @ReactiveVeina did I write a correct version? what I missed?
ero
ero13mo ago
like they mentioned, you probably want to return all implementations of the interface a class could for example do class C : IEnumerable<int>, IEnumerable<long>
if (propertyType.IsClass)
{
if (ImplementsEnumerableTypes(propertyType, out var genericTypes))
{
foreach (var genericType in genericTypes)
{
GeneratePropertiesRecursive(genericType, propertySchema.Properties, processedTypes);
}
}
else
{
GeneratePropertiesRecursive(propertyType, propertySchema.Properties, processedTypes);
}
}
if (propertyType.IsClass)
{
if (ImplementsEnumerableTypes(propertyType, out var genericTypes))
{
foreach (var genericType in genericTypes)
{
GeneratePropertiesRecursive(genericType, propertySchema.Properties, processedTypes);
}
}
else
{
GeneratePropertiesRecursive(propertyType, propertySchema.Properties, processedTypes);
}
}
private bool ImplementsEnumerableTypes(Type type, out IEnumerable<Type> genericTypes)
{
return type
.GetInterfaces()
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.Select(t => t.GetGenericArguments().Single());
}
private bool ImplementsEnumerableTypes(Type type, out IEnumerable<Type> genericTypes)
{
return type
.GetInterfaces()
.Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
.Select(t => t.GetGenericArguments().Single());
}
probably
SWEETPONY
SWEETPONY13mo ago
so hard ok I will try this version, thanks it works perfect now, thanks!
Accord
Accord13mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.