In this article, we will learn how to serialize an object into query string format using C#. We will discuss the challenges we face when dealing with nested objects and arrays and how to deal with them.

To download the source code for this article, you can visit our GitHub repository.

Serialization is a crucial concept in programming, particularly in C#. It involves converting the state of an object into a format such as a stream of bytes and actively storing or transmitting it.

Transforming objects into a query string format becomes essential, especially in web development. Query strings, which consist of key-value pairs appended to a URL, are fundamental for passing parameters between the client and server in HTTP requests.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Before we dive into this topic, we recommend going through how to create a URL query string.

Serialize an Object Into a Query String Using Reflection

In the context of C# programming, transforming objects into query string format involves converting the properties of an object into key-value pairs appended to a URL.

Serialization using reflection involves dynamically inspecting and extracting the properties of an object at runtime and then creating key-value pairs from those properties to form a query string.

Reflection is a powerful feature in C# that allows developers to inspect and interact dynamically with types and objects.

To start with, we’ll create a Book model class:

public class Book
{
    public string Author { get; set; } = "George Orwell";
    public string Language { get; set; } = "English";
}

Now, let’s create a method to serialize an instance of the Book class into a query string using reflection:

private static string ToQueryStringUsingReflection<T>(T obj)
{
    var properties = from p in obj?
                            .GetType()
                            .GetProperties()
                     where p.GetValue(obj, null) != null
                     select $"{HttpUtility.UrlEncode(p.Name)}" +
                     $"={HttpUtility.UrlEncode(p.GetValue(obj)?.ToString())}";

    return string.Join("&", properties);
}

Here we define a generic ToQueryStringUsingReflection<T>() method that accepts an object of type T as the input parameter. After that, using reflection, we get the object’s properties and filter out any that are null. With the help of the HttpUtility.UrlEncode() method, we create a selection of key-value pairs by combining URL-encoded property names and values using the select LINQ clause.  Lastly, we join the key-value pairs into a single string, using “&” as a separator with the help of string.Join(). 

It is important to note, that since we are creating a query string, we must ensure that we properly encode both the keys and values of each query parameter. 

Now, let’s create a method to construct the complete URL:

public static string CreateURLWithBookAsQueryParamsUsingReflection(string url, Book book)
{
    var queryParams = ToQueryStringUsingReflection(book);

    return $"{url}?{queryParams}";
}

Here, we define a CreateURLWithBookAsQueryParamsUsingReflection() method that accepts url and book object as the input parameters. Then, we call the ToQueryStringUsingReflection() method, and concatenate the url and book to form the complete URL.

Let’s proceed to invoke the method:

var baseApiUrl = "https://test.com";
Console.WriteLine(QueryStringSerializer.CreateURLWithBookAsQueryParamsUsingReflection(BaseApiUrl, new Book()));

Here, we initialize the BaseApiUrl and call the CreateURLWithBookAsQueryParamsUsingReflection() method to obtain the complete URL.

Let’s check the final URL:

https://test.com?Author=George+Orwell&Language=English

Here is the final URL with the query parameters.

Serialize an Object Into a Query String With Newtonsoft.Json

Newtonsoft.Json, commonly called Json.NET, is a popular third-party library for JSON serialization and deserialization in .NET applications.

It provides a versatile and efficient way to work with JSON data, making it a widely adopted choice for developers. In the context of serializing an object into a query string, Json.NET offers a straightforward and robust solution.

First, let’s install the Newtonsoft.Json package:

Install-Package Newtonsoft.Json

Next, let’s create a new method in the QueryStringSerializer class:

private static string ToQueryStringUsingNewtonsoftJson<T>(T obj)
{
    string jsonString = JsonConvert.SerializeObject(obj);

    var jsonObject = JObject.Parse(jsonString);

    var properties = jsonObject
        .Properties()
        .Where(p => p.Value.Type != JTokenType.Null)
        .Select(p =>
            $"{HttpUtility.UrlEncode(p.Name)}={HttpUtility.UrlEncode(p.Value.ToString())}");


    return string.Join("&", properties);
}

In a similar fashion to our reflection method, we start by creating a generic ToQueryStringUsingNewtonsoftJson() method. Next, we serialize the object into a JSON string using JsonConvert.SerializeObject().

We then parse the JSON string into a JObject to extract key-value pairs. Similarly, while excluding null values, we create URL-encoded key-value pairs by combining property names and property values. Once again, our final step is to create the query string by joining our key-value pairs with string.Join() using “&” as our separator.

Now, let’s create a method to call our ToQueryStringUsingNewtonsoftJson() method:

public static string CreateURLWithBookAsQueryParamsUsingNewtonsoftJson(string url, Book book)
{
    var queryParams = ToQueryStringUsingNewtonsoftJson(book);

    return $"{url}?{queryParams}";
}

Once again we define a method that accepts url and book object as the input parameters. Then, we invoke the ToQueryStringUsingNewtonsoftJson() method to build the queryParams. Finally, we concatenate the url and queryParams to form the complete URL.

Let’s call the method:

Console.WriteLine(QueryStringSerializer.CreateURLWithBookAsQueryParamsUsingNewtonsoftJson(BaseApiUrl, new Book()));

We invoke the CreateURLWithBookAsQueryParamsUsingNewtonsoftJson() method to build the complete URL.

Let’s inspect the complete URL:

https://test.com?Author=George+Orwell&Language=English

Dealing With Nested Objects During Serialization

When dealing with complex data structures in C# object serialization, one common scenario is handling nested objects.

Nested objects occur when an object contains properties that are themselves objects. Maintaining the structure and relationships within the data is crucial, and ensuring proper serialization of these nested objects is essential.

First, let’s create three model classes, Distributor class:

public class Distributor
{
    public string Name { get; set; } = "TechDistributors";
}

Then, Manufacturer class:

public class Manufacturer
{
    public string Location { get; set; } = "Silicon Valley";
    public Distributor Distributor { get; set; } = new Distributor();
}

We include the Distributor class property as a nested object in the Manufacturer class.

Finally, Product class:

public class Product
{
    public string Name { get; set; } = "Laptop";
    public string Category { get; set; } = "Electronics";
    public Manufacturer Manufacturer { get; set; } = new Manufacturer();
}

Here, we will include the Manufacturer class property as a nested object in the Product class.

We’ll start by defining a method to get the nested property values:

private static IEnumerable<string> GetNestedPropertyValues(object obj)
{
    return obj.GetType().GetProperties()
        .SelectMany(nestedProperty => GetPropertyValues(nestedProperty, obj));
}

Here, we define a GetNestedPropertyValues() method that takes object type as a parameter.

As a first step, we get the runtime type of the object obj and retrieve an array of PropertyInfo. Then, we apply SelectMany LINQ function to flatten the nested properties into a single sequence. We accomplish this by calling our GetPropertyValues() method. 

Next, we need to define GetPropertyValue():

private static IEnumerable<string> GetPropertyValues(PropertyInfo property, object parentObject)
{
    string propertyName = property.Name;
    object propertyValue = property.GetValue(parentObject)!;

    if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
    {

        return GetNestedPropertyValues(propertyValue)
            .Select(nestedValue =>
                $"{HttpUtility.UrlEncode(propertyName)}.{nestedValue}");
    }
    else
    {
        return new[] { $"{HttpUtility.UrlEncode(propertyName)}={HttpUtility.UrlEncode(propertyValue.ToString())}" };
    }
}

Here, we define a GetPropertyValues() method that accepts object type and PropertyInfo as the input parameters.  Next, we extract the property name and value, checking if the property is a class and not a string.

If the property is a nested object (i.e. a class and not a string), we recursively call our GetNestedPropertyValues() method to handle the nested properties. Then, we prefix the property name with the parent property name to maintain the hierarchy. 

In the case where the property is not a nested object, we return a single-element array with a URL-encoded string representation of the property’s name and value.

Now, let’s create a new method to call the GetNestedPropertyValues() method:

public static string NestedObjectToQueryString(Product product)
{
    var propValues = GetNestedPropertyValues(product);
    return string.Join('&', propValues);
}

Here, we define our NestedObjectToQueryString() method that accepts a product object as the input parameter.

First, we call the GetNestedPropertyValues() method passing the product object. Finally, we use the string.Join() method to concatenate the propValues.

So, let’s proceed to invoke the method to build the complete URL:

public static string CreateURLWithProductAsQueryParams(string url, Product product)
{
    var queryParams = NestedObjectToQueryString(product);

    return $"{url}?{queryParams}";
}

In this block, we define a CreateURLWithProductAsQueryParams() method that invokes our NestedObjectToQueryString() method to obtain the queryParams and return the full URL.

Let’s see it in action:

Console.WriteLine(QueryStringSerializer.CreateURLWithProductAsQueryParams(BaseApiUrl, new Product()));

Let’s see the output:

https://test.com?Name=Laptop&Category=Electronics&Manufacturer.Location=Silicon+Valley&Manufacturer.Distributor.Name=TechDistributors

Here, Manufacturer.Location indicates that the Location property is nested within the Manufacturer property. Similarly, Manufacturer.Distributor.Name indicates that the Name property of the Distributor object is nested within the Manufacturer property. 

How to Handle Arrays and Nested Objects During Serialization

Serializing arrays and collections introduces unique challenges that require careful consideration.

Unlike single values or simple objects, arrays and collections can contain multiple elements, and appropriately serializing each element becomes necessary.

Let’s proceed to create two model classes, Address class:

public class Address
{
    public string Country { get; set; } = "Australia";
}

And Person class:

public class Person
{
    public string FirstName { get; set; } = "Smith";
    public int Age { get; set; } = 25;
    public string[] Hobbies { get; set; } = { "Reading", "Traveling", "Gaming" };
    public Address Address { get; set; } = new Address();
}

Here, we initialize the Hobbies properties as a string array and include the Address class property as a nested object in the Person class.

Let’s create a method to get the array values:

private static IEnumerable<string> GetArrayValues(string propertyName, Array array)
{
    return Enumerable.Range(0, array.Length)
        .Select(i =>
        {
            var arrayElementValue = HttpUtility.UrlEncode(array.GetValue(i)?.ToString()) ?? string.Empty;
            var arrayPropName = propertyName + $"[{i}]";
            return $"{HttpUtility.UrlEncode(arrayPropName)}={arrayElementValue}";
        });
}

We define a GetArrayValues() method that accepts propertyName and array as the input parameter.

Then, using Enumerable.Range to create a sequence of indices, we generate key-value pairs for each array element by combining the property name with the index enclosed within square brackets: MyPropertyName[i]. We then URL-encode this key value along with the array elements value to form our key-value pair. It is important to note that according to Appendix A of RFC 3986, we need to be sure to URL-encode both propertyName and the square brackets in our query string.

If an array element happens to be null, the resulting string in the query parameter could appear as “MyPropertyName[i]=”. Although technically valid, it may lead to unexpected results when the query string is parsed.

Now, let’s update our existing GetPropertyValues() method to also deal with arrays:

private static IEnumerable<string> GetPropertyValues(PropertyInfo property, object parentObject)
{
    string propertyName = property.Name;
    object propertyValue = property.GetValue(parentObject)!;

    if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
    {
        if (property.PropertyType.IsArray)
        {
            return GetArrayValues(propertyName, (Array)propertyValue);
        }
        else
        {
            return GetNestedPropertyValues(propertyValue)
                .Select(nestedValue =>
                    $"{HttpUtility.UrlEncode(propertyName)}.{HttpUtility.UrlEncode(nestedValue)}");
        }
    }
    else
    {
        return new[] { $"{HttpUtility.UrlEncode(propertyName)}={HttpUtility.UrlEncode(propertyValue.ToString())}" };
    }
}

Here, we have added a check for array properties which calls the corresponding GetArrayValues() method passing the property name and the property value cast as an array.

Now, let’s define a new method to exercise our array serializer:

private static string ObjectWithArrayAndNestedObjectToQueryString(Person person)
{
    var propValues = GetNestedPropertyValues(person);

    var finalQueryString = string.Join('&', propValues);

    return finalQueryString;
}

Our new method takes a Person object and subsequently invokes the existing GetNestedPropertyValues() method to obtain the propValues to build the final query string. Once again our final step invokes string.Join() to form the finalQueryString.

Let’s create another method to invoke the ObjectWithArrayToQueryString() method:

public static string CreateURLWithPersonAsQueryParams(string url, Person person)
{
    var queryParams = ObjectWithArrayToQueryString(person);

    return $"{url}?{queryParams}";
}

Our new CreateURLWithPersonAsQueryParams() method accepts a url and a person object as the input parameters. Inside the method we call ObjectWithArrayToQueryString() to build the queryParams, and finally, combine that with our base URL and return the whole string.

So, let’s proceed to call the method:

Console.WriteLine(QueryStringSerializer.CreateURLWithPersonAsQueryParams(BaseApiUrl, new Person()));

Let’s invoke the CreateURLWithPersonAsQueryParams() method to frame the complete URL.

Let’s inspect the final URL:

https://test.com?FirstName=Smith&Age=25&Hobbies%5b0%5d=Reading&Hobbies%5b1%5d=Traveling&Hobbies%5b2%5d=Gaming&Address.Country=Australia

Here, the use of square brackets (URL-encoded as %5b and %5d respectively) and an index indicates that the values belong to the elements of an array in the Person class and Address.Country indicates that the Country property is nested within the Address property.

Conclusion

In this article, we have seen how to serialize an object into query string format using reflection and the third-party library Newtonsoft.Json. We also learned how to deal with nested objects and arrays during serialization. 

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!