Last week while I was building a wiki generator and realized that I have to generate the possible responses of an API. Which means I need to get an object with all meaningful values in it. For example:
{
"value": {
"id": "69c8981d-3498-48ff-8126-fcee0e8c8929",
"conversationId": 0,
"ats": [
{
"targetUserId": "an example string."
}
],
"senderId": "an example string.",
"sender": {
"email": "an example string.",
"isOnline": true,
"id": "an example string.",
"nickName": "an example string.",
"iconFilePath": "an example string."
},
"sendTime": "2019-12-25T08:52:03.2898292Z",
"content": "an example string.",
"read": true
},
"code": 0,
"message": "an example string."
}
Reading and writing that JSON is super easy. But how can we build it without knowing the structure of the class? So I built an example object injector.
To build an object, I need to give some example values based on the type input.
public static object Make(Type type)
{
if (type == typeof(string))
{
return "an example string.";
}
else if (type == typeof(int) || type == typeof(int?))
{
return 0;
}
else if (type == typeof(DateTime) || type == typeof(DateTime?))
{
return DateTime.UtcNow;
}
else if (type == typeof(Guid) || type == typeof(Guid?))
{
return Guid.NewGuid();
}
else if (type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?))
{
return DateTimeOffset.UtcNow;
}
else if (type == typeof(TimeSpan) || type == typeof(TimeSpan?))
{
return TimeSpan.FromMinutes(37);
}
else if (type == typeof(bool) || type == typeof(bool?))
{
return true;
}
// not finihsed..
}
For example, if you require a bool
, I will return you true.
If you require a string
, I will return you an example string.
.
But what if the program requires an array or a list? Consider the user wants a List<string>
. First, we need to get the type string
.
// continue
else if (type.IsGenericType && type.GetGenericTypeDefinition().GetInterfaces().Any(t => t.IsAssignableFrom(typeof(IEnumerable))))
{
var itemType = type.GetGenericArguments()[0];
// not finished.
}
While we currently know the type of the array element, we still can't just return Make(itemType)
. For in some cases the itemType
might be abstract. Consider the following cases:
public abstract class Car
{
}
public class Truck : Car
{
}
public class BenchCar : Car
{
}
When the input is List<Car>
, the best response is new List() { new Truck(), new BenchCar() }
which indicates all possible value structures in the collection.
So we need to search for all possible types to fill the collection. Write a new method like this:
public static IList GetArrayWithInstanceInherts(Type itemType)
{
var listType = typeof(List<>);
var constructedListType = listType.MakeGenericType(itemType);
var instance = (IList)Activator.CreateInstance(constructedListType);
if (!itemType.IsAbstract)
{
instance.Add(Make(itemType));
}
foreach (var item in Assembly.GetEntryAssembly().GetTypes().Where(t => !t.IsAbstract).Where(t => t.IsSubclassOf(itemType)))
{
instance.Add(Make(item));
}
return instance;
}
That method will return an IList
with all possible instances which inherit the input itemType
.
Continue changing the method Make
to return the list:
// List
else if (type.IsGenericType && type.GetGenericTypeDefinition().GetInterfaces().Any(t => t.IsAssignableFrom(typeof(IEnumerable))))
{
var itemType = type.GetGenericArguments()[0];
return GetArrayWithInstanceInherts(itemType);
}
As for the array, we also need to get the type of its element and inject the list.
// Array
else if (type.GetInterface(typeof(IEnumerable<>).FullName) != null)
{
var itemType = type.GetElementType();
var list = GetArrayWithInstanceInherts(itemType);
var array = Array.CreateInstance(itemType, list.Count);
list.CopyTo(array, 0);
return array;
}
Now, for all input with common types and collections, we can return an instance as an example correctly. But what if the input cascades another object?
We also need to provide a method to get an object directly.
public static object GenerateWithConstructor(Type type)
{
if (type.IsAbstract)
{
return null;
}
else if (!type.GetConstructors().Any(t => t.IsPublic))
{
return null;
}
else
{
return Assembly.GetAssembly(type).CreateInstance(type.FullName);
}
}
This returns an example object from the input user input. But consider the following cases:
public class MyClass
{
public MyClass(string input)
{
MyString = input;
}
public string MyString { get; set; }
}
The class MyClass
doesn't have a constructor without any parameters. We have to inject the input
to build the object.
else if (type.GetConstructors().Where(t => t.IsPublic).Any() && !type.IsAbstract)
{
// Has a constructor, and constructor has some arguments.
var constructor = type.GetConstructors()[0];
var args = constructor.GetParameters();
object[] parameters = new object[args.Length];
for (int i = 0; i < args.Length; i++)
{
var requirement = args[i].ParameterType;
parameters[i] = Make(requirement);
}
return Assembly.GetAssembly(type).CreateInstance(type.FullName, true, BindingFlags.Default, null, parameters, null, null);
}
This finishes our GenerateWithConstructor
method. It finally looks like this:
public static object GenerateWithConstructor(Type type)
{
// Has default constructor.
if (type.GetConstructors().Count() == 1 &&
type.GetConstructors()[0].GetParameters().Count() == 0 &&
!type.IsAbstract)
{
return Assembly.GetAssembly(type).CreateInstance(type.FullName);
}
else if (type.GetConstructors().Where(t => t.IsPublic).Any() && !type.IsAbstract)
{
// Has a constructor, and constructor has some arguments.
var constructor = type.GetConstructors()[0];
var args = constructor.GetParameters();
object[] parameters = new object[args.Length];
for (int i = 0; i < args.Length; i++)
{
var requirement = args[i].ParameterType;
parameters[i] = Make(requirement);
}
return Assembly.GetAssembly(type).CreateInstance(type.FullName, true, BindingFlags.Default, null, parameters, null, null);
}
else if (type.IsAbstract)
{
return null;
}
else if (!type.GetConstructors().Any(t => t.IsPublic))
{
return null;
}
else
{
return Assembly.GetAssembly(type).CreateInstance(type.FullName);
}
}
And go back to our Make
method. To finally return the cascaded object, just call GenerateWithConstructor
to get it.
else
{
var instance = GenerateWithConstructor(type);
return instance;
}
This looks fine, but still not finished. The properties in your constructed object may still contain other properties. We also need to inject it.
The best practice to inject all properties is to do it recursively. Just call the Make
method inside the Make
method.
else
{
var instance = GenerateWithConstructor(type);
if (instance != null)
{
foreach (var property in instance.GetType().GetProperties())
{
if (property.SetMethod != null)
{
property.SetValue(instance, Make(property.PropertyType));
}
}
}
return instance;
}
Now everything works fine. Finally, our code looks like this:
public static object Make(Type type)
{
if (type == typeof(string))
{
return "an example string.";
}
else if (type == typeof(int) || type == typeof(int?))
{
return 0;
}
else if (type == typeof(DateTime) || type == typeof(DateTime?))
{
return DateTime.UtcNow;
}
else if (type == typeof(Guid) || type == typeof(Guid?))
{
return Guid.NewGuid();
}
else if (type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?))
{
return DateTimeOffset.UtcNow;
}
else if (type == typeof(TimeSpan) || type == typeof(TimeSpan?))
{
return TimeSpan.FromMinutes(37);
}
else if (type == typeof(bool) || type == typeof(bool?))
{
return true;
}
// List
else if (type.IsGenericType && type.GetGenericTypeDefinition().GetInterfaces().Any(t => t.IsAssignableFrom(typeof(IEnumerable))))
{
var itemType = type.GetGenericArguments()[0];
return GetArrayWithInstanceInherts(itemType);
}
// Array
else if (type.GetInterface(typeof(IEnumerable<>).FullName) != null)
{
var itemType = type.GetElementType();
var list = GetArrayWithInstanceInherts(itemType);
var array = Array.CreateInstance(itemType, list.Count);
list.CopyTo(array, 0);
return array;
}
else
{
var instance = GenerateWithConstructor(type);
if (instance != null)
{
foreach (var property in instance.GetType().GetProperties())
{
if (property.SetMethod != null)
{
property.SetValue(instance, Make(property.PropertyType));
}
}
}
return instance;
}
}
It's time for us to test it! Build some classes and try to inject an object!
public abstract class Conversation
{
}
public class PConversation : Conversation
{
public string MYCName { get; set; }
}
public class GConversation : Conversation
{
public string[] MGName { get; set; }
}
public enum Gender
{
male,
female
}
public class Son
{
public string Name { get; set; }
public Gender Gender { get; set; }
}
public class MyClass
{
public int Count { get; set; }
public string Message { get; set; }
public List<string> Teachers1 { get; set; }
public string[] Teachers2 { get; set; }
public List<Son> Sons { get; set; }
public Son[] Sons2 { get; set; }
public Son MySon { get; set; }
public bool IsMale { get; set; }
public Conversation[] Conversations { get; set; }
public List<Conversation> Conversations2 { get; set; }
}
Test it with:
static void Main(string[] args)
{
var instance = Make(typeof(MyClass));
var finalresult = JsonConvert.SerializeObject(instance, Formatting.Indented);
Console.WriteLine(finalresult);
}
Result:
{
"Count": 0,
"Message": "an example string.",
"Teachers1": [
"an example string."
],
"Teachers2": [
"an example string."
],
"Sons": [
{
"Name": "an example string.",
"Gener": 0
}
],
"Sons2": [
{
"Name": "an example string.",
"Gener": 0
}
],
"MySon": {
"Name": "an example string.",
"Gener": 0
},
"IsMale": true,
"Conversations": [
{
"MYCName": "an example string."
},
{
"MGName": [
"an example string."
]
}
],
"Conversations2": [
{
"MYCName": "an example string."
},
{
"MGName": [
"an example string."
]
}
]
}
I appreciate your detailed explanation of how to create an instance of a class with all default values. The blog post is well-structured, and the code snippets are easy to understand. The core idea of using recursion to inject properties and generate cascaded objects is quite clever.
The example you provided at the end of the post is helpful in demonstrating the practical application of the code. The output of the test case is clear and easy to understand.
However, there are a few areas where the blog post could be improved:
It would be helpful to provide a brief introduction at the beginning of the post, explaining the purpose of the code and the problem it solves. This would give the readers a better understanding of the context and motivation behind the code.
In the code snippet, there are some minor formatting inconsistencies, such as inconsistent indentation and spacing. It's essential to maintain a consistent code style for better readability.
Some comments in the code snippet are not very clear or informative. For example, the comment "// List" could be more descriptive, like "// Handle List<T> types." Providing more informative comments will help the reader understand the code more easily.
The blog post could benefit from a brief conclusion, summarizing the main points and any potential limitations or future improvements that could be made to the code.
Overall, your blog post is informative and provides a valuable solution for generating instances of classes with default values. By addressing the points mentioned above, you can make it even more helpful and accessible to your readers. Keep up the good work!