How do I serialize an object into query-string format?
Asked Answered
D

15

102

How do I serialize an object into query-string format? I can't seem to find an answer on google. Thanks.

Here is the object I will serialize as an example.

public class EditListItemActionModel
{
    public int? Id { get; set; }
    public int State { get; set; }
    public string Prefix { get; set; }
    public string Index { get; set; }
    public int? ParentID { get; set; }
}
Denary answered 27/7, 2011 at 17:0 Comment(13)
Why not create your own function to serialize this way?Tipple
You want to end up with: Id=1&State=CA&Prefix=Mr... something like that? If so, I'm agreeing with @James.Fadeless
@James Wow, is that the only way? I figured there was something built into .NET somewhere. I'm thinking kind of like the inverse of the MVC model binder. There must be a method for this right?Denary
If there is no built in function, can you give me a clue how to write one?Denary
Are you using ASP.NET MVC or plain ASP.NET?Feltonfelts
@A. Can't the necessary namespace be imported either way?Denary
Flurl is a URL builder/HTTP client that uses objects extensively for name-value-pair-like things (query strings, headers, URL-encoded form values, etc). SetQueryParams does exactly what you're looking for. If you just want the URL builder and not all the HTTP stuff, it's available here. [disclaimer: I'm the author]Rosemarie
@ToddMenier Nice library, but how can I use it for camel cased serialization?Wicopy
@PavelBiryukov You want TitleCase object properties serialized to camelCase query string values? Flurl can't do that, it takes the property names as-is.Rosemarie
@ToddMenier That would be a nice option :) Like in asp.net core (web api) you can configure serialization to json as camelCase (default, which is common for javascript) or TitleCase. I've made a hepler method using Microsoft.AspNetCore.WebUtilities.QueryHelpers at last...Wicopy
@PavelBiryukov Are you talking about JSON in the body or JSON in the query string? For the body, Flurl's default serializer is Json.NET and you can easily hook into its settings for camel case support. See this issue (toward the bottom). If you're talking about JSON in the query string, that's not a very common thing.Rosemarie
@ToddMenier I mean LIKE in asp.net core...Wicopy
This is the other solution that you may want to take a look ole.michelsen.dk/blog/…Noland
E
142

I'm 99% sure there's no built-in utility method for this. It's not a very common task, since a web server doesn't typically respond with a URLEncoded key/value string.

How do you feel about mixing reflection and LINQ? This works:

var foo = new EditListItemActionModel() {
  Id = 1,
  State = 26,
  Prefix = "f",
  Index = "oo",
  ParentID = null
};

var properties = from p in foo.GetType().GetProperties()
                 where p.GetValue(foo, null) != null
                 select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(foo, null).ToString());

// queryString will be set to "Id=1&State=26&Prefix=f&Index=oo"                  
string queryString = String.Join("&", properties.ToArray());

Update:

To write a method that returns the QueryString representation of any 1-deep object, you could do this:

public string GetQueryString(object obj) {
  var properties = from p in obj.GetType().GetProperties()
                   where p.GetValue(obj, null) != null
                   select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(obj, null).ToString());

  return String.Join("&", properties.ToArray());
}

// Usage:
string queryString = GetQueryString(foo);

You could also make it an extension method without much additional work

public static class ExtensionMethods {
  public static string GetQueryString(this object obj) {
    var properties = from p in obj.GetType().GetProperties()
                     where p.GetValue(obj, null) != null
                     select p.Name + "=" + HttpUtility.UrlEncode(p.GetValue(obj, null).ToString());

    return String.Join("&", properties.ToArray());
  }
}

// Usage:
string queryString = foo.GetQueryString();
Etka answered 27/7, 2011 at 17:35 Comment(5)
This is nice. I'm trying to make it a function that takes a dynamic parameter but I assume I am messing up the dynamic linq select syntax. public string SerializeWithDynamicLINQ(dynamic Thing) { var Properties = Thing.GetType().GetProperties().ToArray(); return "&" + Properties.select("Property.Name") + "=" + HttpUtility.UrlEncode( Properties.select("Property").GetValue(Thing, null).ToString()); } I can't figure out how to do code blocks in comments either..Denary
@Benjamin: Updated my answer to help with that.Etka
Since you say it's not a very common task. What is the alternative approach to pass in lots of form values, without having to hard code all the RouteValueDictionary valuesBassett
You can make this more efficient by getting the value of each property only once by assigning it to a temporary variable, e.g. using let value = p.GetValue(obj, null).Windermere
This works only for English Culture Info on server. If you set specific datetime format on Windows, this will not work, because you should set CultureInfo Invariant as ToString parameter. The same problem is with specific decimal separator in float/double.Dye
V
25

Using Json.Net it would be much easier, by serializing and then deserializing to key value pairs.

Here is a code example:

using Newtonsoft.Json;
using System.Web;

string ObjToQueryString(object obj)
{
     var step1 = JsonConvert.SerializeObject(obj);

     var step2 = JsonConvert.DeserializeObject<IDictionary<string, string>>(step1);

     var step3 = step2.Select(x => HttpUtility.UrlEncode(x.Key) + "=" + HttpUtility.UrlEncode(x.Value));

     return string.Join("&", step3);
}
Val answered 10/7, 2019 at 19:16 Comment(3)
I used this as it worked out the box for DateTime propertiesEnrobe
I like the simplicity of this. It's great for flat objects, but no good for nested objects/listsTurner
This works with the System.Text.Json library as well, just make sure the IDictionary in step 2 is <string, object> instead, and then use x.Value.ToString() in step 3.Rightminded
E
22

Building on the good ideas from other comments, I have made a generic extension method .ToQueryString(), which can be used on any object.

public static class UrlHelpers
{
    public static string ToQueryString(this object request, string separator = ",")
    {
        if (request == null)
            throw new ArgumentNullException("request");

        // Get all properties on the object
        var properties = request.GetType().GetProperties()
            .Where(x => x.CanRead)
            .Where(x => x.GetValue(request, null) != null)
            .ToDictionary(x => x.Name, x => x.GetValue(request, null));

        // Get names for all IEnumerable properties (excl. string)
        var propertyNames = properties
            .Where(x => !(x.Value is string) && x.Value is IEnumerable)
            .Select(x => x.Key)
            .ToList();

        // Concat all IEnumerable properties into a comma separated string
        foreach (var key in propertyNames)
        {
            var valueType = properties[key].GetType();
            var valueElemType = valueType.IsGenericType
                                    ? valueType.GetGenericArguments()[0]
                                    : valueType.GetElementType();
            if (valueElemType.IsPrimitive || valueElemType == typeof (string))
            {
                var enumerable = properties[key] as IEnumerable;
                properties[key] = string.Join(separator, enumerable.Cast<object>());
            }
        }

        // Concat all key/value pairs into a string separated by ampersand
        return string.Join("&", properties
            .Select(x => string.Concat(
                Uri.EscapeDataString(x.Key), "=",
                Uri.EscapeDataString(x.Value.ToString()))));
    }
}

It will also work for objects that have properties of the type Array and generic Lists if they only contain primitives or strings.

Try it out, comments are welcome: Serialize object into a query string with Reflection

Ensheathe answered 15/9, 2012 at 11:35 Comment(2)
Why don't you put the code here. It's not that much.Baccy
Just a little syntax sugar if (request == null) throw new ArgumentNullException(nameof(request));Hazelton
E
13

Based on the the popular answers, I needed to update the code to support arrays as well. Sharing the implementation:

public string GetQueryString(object obj)
{
    var result = new List<string>();
    var props = obj.GetType().GetProperties().Where(p => p.GetValue(obj, null) != null);
    foreach (var p in props)
    {
        var value = p.GetValue(obj, null);
        var enumerable = value as ICollection;
        if (enumerable != null)
        {
            result.AddRange(from object v in enumerable select string.Format("{0}={1}", p.Name, HttpUtility.UrlEncode(v.ToString())));
        }
        else
        {
            result.Add(string.Format("{0}={1}", p.Name, HttpUtility.UrlEncode(value.ToString())));
        }
    }

    return string.Join("&", result.ToArray());
}
Edouard answered 6/6, 2016 at 12:22 Comment(0)
S
5

It will also be useful for nested objects

public static class HttpQueryStrings
{
    private static readonly StringBuilder _query = new();

    public static string ToQueryString<T>(this T @this) where T : class
    {
        _query.Clear();

        BuildQueryString(@this, "");

        if (_query.Length > 0) _query[0] = '?';

        return _query.ToString();
    }

    private static void BuildQueryString<T>(T? obj, string prefix = "") where T : class
    {
        if (obj == null) return;

        foreach (var p in obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            if (p.GetValue(obj, Array.Empty<object>()) != null)
            {
                var value = p.GetValue(obj, Array.Empty<object>());


                if (p.PropertyType.IsArray && value?.GetType() == typeof(DateTime[]))
                    foreach (var item in (DateTime[])value)
                        _query.Append($"&{prefix}{p.Name}={item.ToString("yyyy-MM-dd")}");

                else if (p.PropertyType.IsArray)
                    foreach (var item in (Array)value!)
                        _query.Append($"&{prefix}{p.Name}={item}");

                else if (p.PropertyType == typeof(string))
                    _query.Append($"&{prefix}{p.Name}={value}");

                else if (p.PropertyType == typeof(DateTime) && !value!.Equals(Activator.CreateInstance(p.PropertyType))) // is not default 
                    _query.Append($"&{prefix}{p.Name}={((DateTime)value).ToString("yyyy-MM-dd")}");

                else if (p.PropertyType.IsValueType && !value!.Equals(Activator.CreateInstance(p.PropertyType))) // is not default 
                    _query.Append($"&{prefix}{p.Name}={value}");


                else if (p.PropertyType.IsClass)
                    BuildQueryString(value, $"{prefix}{p.Name}.");
            }
        }
    }
}

An example of using the solution:

string queryString = new
{
    date = new DateTime(2020, 1, 1),
    myClass = new MyClass
    {
        FirstName = "john",
        LastName = "doe"
    },
    myArray = new int[] { 1, 2, 3, 4 },
}.ToQueryString();
Sycophancy answered 30/3, 2020 at 17:32 Comment(0)
P
4

Perhaps this Generic approach will be useful:

    public static string ConvertToQueryString<T>(T entity) where T: class
    {
        var props = typeof(T).GetProperties();

        return $"?{string.Join('&', props.Where(r=> r.GetValue(entity) != null).Select(r => $"{HttpUtility.UrlEncode(r.Name)}={HttpUtility.UrlEncode(r.GetValue(entity).ToString())}"))}";
    }
Panthia answered 22/5, 2019 at 15:56 Comment(1)
Does it work on nested properties that have their properties?Streetlight
P
3
public static class UrlHelper
{
      public static string ToUrl(this Object instance)
      {
            var urlBuilder = new StringBuilder();
            var properties = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
            for (int i = 0; i < properties.Length; i++)
            {
                var value = properties[i].GetValue(instance, null);
                if (value is DateTime)
                {
                    value = ((DateTime)value).ToString("s", CultureInfo.InvariantCulture);
                }
                value = HttpUtility.UrlEncode(value.ToString());
                urlBuilder.AppendFormat("{0}={1}&", properties[i].Name, value);
             }
             if (urlBuilder.Length > 1)
             {
                urlBuilder.Remove(urlBuilder.Length - 1, 1);
             }
             return urlBuilder.ToString();
       }
 }
Purapurblind answered 27/6, 2017 at 11:19 Comment(1)
This breaks with date values.Organization
C
3

This my solution:

public static class ObjectExtensions
{
    public static string ToQueryString(this object obj)
    {
        if (!obj.GetType().IsComplex())
        {
            return obj.ToString();
        }

        var values = obj
            .GetType()
            .GetProperties()
            .Where(o => o.GetValue(obj, null) != null);

        var result = new QueryString();

        foreach (var value in values)
        {
            if (!typeof(string).IsAssignableFrom(value.PropertyType) 
                && typeof(IEnumerable).IsAssignableFrom(value.PropertyType))
            {
                var items = value.GetValue(obj) as IList;
                if (items.Count > 0)
                {
                    for (int i = 0; i < items.Count; i++)
                    {
                        result = result.Add(value.Name, ToQueryString(items[i]));
                    }
                }
            }
            else if (value.PropertyType.IsComplex())
            {
                result = result.Add(value.Name, ToQueryString(value));
            }
            else
            {
                result = result.Add(value.Name, value.GetValue(obj).ToString());
            }
        }

        return result.Value;
    }

    private static bool IsComplex(this Type type)
    {
        var typeInfo = type.GetTypeInfo();
        if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            // nullable type, check if the nested type is simple.
            return IsComplex(typeInfo.GetGenericArguments()[0]);
        }
        return !(typeInfo.IsPrimitive
          || typeInfo.IsEnum
          || type.Equals(typeof(Guid))
          || type.Equals(typeof(string))
          || type.Equals(typeof(decimal)));
    }
}

I use this extension for my integration test, it works perfectly :)

Celeski answered 17/8, 2018 at 23:59 Comment(0)
C
2

Just another variation of the above, but I wanted to utilize the existing DataMember attributes in my model class, so only the properties I want to serialize are sent to the server in the url in the GET request.

    public string ToQueryString(object obj)
    {
        if (obj == null) return "";

        return "?" + string.Join("&", obj.GetType()
                                   .GetProperties()
                                   .Where(p => Attribute.IsDefined(p, typeof(DataMemberAttribute)) && p.GetValue(obj, null) != null)
                                   .Select(p => $"{p.Name}={Uri.EscapeDataString(p.GetValue(obj).ToString())}"));
    }
Chambliss answered 26/7, 2018 at 17:47 Comment(0)
C
1

Here is something I wrote that does what you need.

    public string CreateAsQueryString(PageVariables pv) //Pass in your EditListItemActionModel instead
    {
        int i = 0;
        StringBuilder sb = new StringBuilder();

        foreach (var prop in typeof(PageVariables).GetProperties())
        {
            if (i != 0)
            {
                sb.Append("&");
            }

            var x = prop.GetValue(pv, null).ToString();

            if (x != null)
            {
                sb.Append(prop.Name);
                sb.Append("=");
                sb.Append(x.ToString());
            }

            i++;
        }

        Formating encoding = new Formating();
        // I am encoding my query string - but you don''t have to
        return "?" + HttpUtility.UrlEncode(encoding.RC2Encrypt(sb.ToString()));  
    }
Combined answered 27/7, 2011 at 17:18 Comment(3)
That could just as easily take object.Xanthippe
@Combined Thank you! I will try it out. I am surprised there is nothing built in. Will this catch inherited properties also?Denary
Benjamin, I think so - I don't totally remember as I wrote this code awhile ago but remembered I had when I saw your question.Combined
W
1

In addition to existing answers

public static string ToQueryString<T>(this T input)
        {
            if (input == null)
            {
                return string.Empty;
            }
                
            var queryStringBuilder = new StringBuilder("?");

            var properties = input.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);

            foreach (var property in properties)
            {
                var value = property.GetValue(input);
                if (value is null || property.HasIgnoreDataMember())
                    continue;

                queryStringBuilder.AppendFormat("{0}={1}&", property.GetName(), HttpUtility.UrlEncode(value.ToString()));
            }
            queryStringBuilder.Length--;

            return queryStringBuilder.ToString();
        }

        private static bool HasIgnoreDataMember(this PropertyInfo propertyInfo)
        {
            return propertyInfo.GetCustomAttribute(typeof(IgnoreDataMemberAttribute), true) is not null;
        }

        private static DataMemberAttribute GetDataMemberAttribute(this PropertyInfo propertyInfo)
        {
            return propertyInfo.GetCustomAttribute<DataMemberAttribute>();
        }

        private static T GetCustomAttribute<T>(this PropertyInfo propertyInfo) where T : class
        {
            return propertyInfo.GetCustomAttribute(typeof(T), true) as T;
        }

        private static string GetName(this PropertyInfo propertyInfo)
        {
            return propertyInfo.GetDataMemberAttribute()?.Name ?? propertyInfo.Name;
        }
    }

Usage: var queryString = object.ToQueryString()

Wert answered 23/7, 2022 at 7:14 Comment(0)
R
0

I was looking for a solution to this for a Windows 10 (UWP) App. Taking the Relection approach suggested by Dave, and after adding the Microsoft.AspNet.WebApi.Client Nuget package, I used the following code, which handles Url Encoding of the property values:

 private void AddContentAsQueryString(ref Uri uri, object content)
    {            
        if ((uri != null) && (content != null))
        {
            UriBuilder builder = new UriBuilder(uri);

            HttpValueCollection query = uri.ParseQueryString();

            IEnumerable<PropertyInfo> propInfos = content.GetType().GetRuntimeProperties();

            foreach (var propInfo in propInfos)
            {
                object value = propInfo.GetValue(content, null);
                query.Add(propInfo.Name, String.Format("{0}", value));
            }

            builder.Query = query.ToString();
            uri = builder.Uri;                
        }
    }
Relational answered 10/3, 2016 at 10:17 Comment(0)
S
0

A simple approach that supports list properties:

public static class UriBuilderExtensions
{
    public static UriBuilder SetQuery<T>(this UriBuilder builder, T parameters)
    {
        var fragments = typeof(T).GetProperties()
            .Where(property => property.CanRead)
            .Select(property => new
            {
                property.Name,
                Value = property.GetMethod.Invoke(parameters, null)
            })
            .Select(pair => new
            {
                pair.Name,
                List = (!(pair.Value is string) && pair.Value is IEnumerable list ? list.Cast<object>() : new[] { pair.Value })
                    .Select(element => element?.ToString())
                    .Where(element => !string.IsNullOrEmpty(element))
            })
            .Where(pair => pair.List.Any())
            .SelectMany(pair => pair.List.Select(value => Uri.EscapeDataString(pair.Name) + '=' + Uri.EscapeDataString(value)));

        builder.Query = string.Join("&", fragments);
        return builder;
    }
}

A faster solution which is as fast as spelling out the code to serialize each type:

public static class UriBuilderExtensions
{
    public static UriBuilder SetQuery<TSource>(this UriBuilder builder, TSource parameters)
    {
        var fragments = Cache<TSource>.Properties
            .Select(property => new
            {
                property.Name,
                List = property.FetchValue(parameters)?.Where(item => !string.IsNullOrEmpty(item))
            })
            .Where(parameter => parameter.List?.Any() ?? false)
            .SelectMany(pair => pair.List.Select(item => Uri.EscapeDataString(pair.Name) + '=' + Uri.EscapeDataString(item)));

        builder.Query = string.Join("&", fragments);
        return builder;
    }

    /// <summary>
    /// Caches dynamically emitted code which converts a types getter property values to a list of strings.
    /// </summary>
    /// <typeparam name="TSource">The type of the object being serialized</typeparam>
    private static class Cache<TSource>
    {
        public static readonly IEnumerable<IProperty> Properties =
            typeof(TSource).GetProperties()
            .Where(propertyInfo => propertyInfo.CanRead)
            .Select(propertyInfo =>
            {
                var source = Expression.Parameter(typeof(TSource));
                var getter = Expression.Property(source, propertyInfo);
                var cast = Expression.Convert(getter, typeof(object));
                var expression = Expression.Lambda<Func<TSource, object>>(cast, source).Compile();
                return new Property
                {
                    Name = propertyInfo.Name,
                    FetchValue = typeof(IEnumerable).IsAssignableFrom(propertyInfo.PropertyType) && propertyInfo.PropertyType != typeof(string) ?
                        CreateListFetcher(expression) :
                        CreateValueFetcher(expression)
                };
            })
            .OrderBy(propery => propery.Name)
            .ToArray();

        /// <summary>
        /// Creates a function which serializes a <see cref="IEnumerable"/> property value to a list of strings.
        /// </summary>
        /// <param name="get">A lambda function which retrieves the property value from a given source object.</param>
        private static Func<TSource, IEnumerable<string>> CreateListFetcher(Func<TSource, object> get)
           => obj => ((IEnumerable)get(obj))?.Cast<object>().Select(item => item?.ToString());

        /// <summary>
        /// Creates a function which serializes a <see cref="object"/> property value to a list of strings.
        /// </summary>
        /// <param name="get">A lambda function which retrieves the property value from a given source object.</param>
        private static Func<TSource, IEnumerable<string>> CreateValueFetcher(Func<TSource, object> get)
            => obj => new[] { get(obj)?.ToString() };

        public interface IProperty
        {
            string Name { get; }
            Func<TSource, IEnumerable<string>> FetchValue { get; }
        }

        private class Property : IProperty
        {
            public string Name { get; set; }
            public Func<TSource, IEnumerable<string>> FetchValue { get; set; }
        }
    }
}

An example of using either solution:

var url = new UriBuilder("test.com").SetQuerySlow(new
{
    Days = new[] { WeekDay.Tuesday, WeekDay.Wednesday },
    Time = TimeSpan.FromHours(14.5),
    Link = "conferences.com/apple/stream/15",
    Pizzas = default(int?)
}).Uri;

Output:
http://test.com/Days=Tuesday&Days=Wednesday&Time=14:30:00&Link=conferences.com%2Fapple%2Fstream%2F15
Neither of the solutions handle exotic types, indexed parameters, or nested parameters.

When manual serialization is simpler, this c#7/.net4.7 approach can help:

public static class QueryParameterExtensions
{
    public static UriBuilder SetQuery(this UriBuilder builder, params (string Name, object Obj)[] parameters)
    {
        var list = parameters
            .Select(parameter => new
            {
                parameter.Name,
                Values = SerializeToList(parameter.Obj).Where(value => !string.IsNullOrEmpty(value))
            })
            .Where(parameter => parameter.Values.Any())
            .SelectMany(parameter => parameter.Values.Select(item => Uri.EscapeDataString(parameter.Name) + '=' + Uri.EscapeDataString(item)));
        builder.Query = string.Join("&", list);
        return builder;
    }

    private static IEnumerable<string> SerializeToList(object obj)
    {
        switch (obj)
        {
            case string text:
                yield return text;
                break;
            case IEnumerable list:
                foreach (var item in list)
                {
                    yield return SerializeToValue(item);
                }
                break;
            default:
                yield return SerializeToValue(obj);
                break;
        }
    }

    private static string SerializeToValue(object obj)
    {
        switch (obj)
        {
            case bool flag:
                return flag ? "true" : null;
            case byte number:
                return number == default(byte) ? null : number.ToString();
            case short number:
                return number == default(short) ? null : number.ToString();
            case ushort number:
                return number == default(ushort) ? null : number.ToString();
            case int number:
                return number == default(int) ? null : number.ToString();
            case uint number:
                return number == default(uint) ? null : number.ToString();
            case long number:
                return number == default(long) ? null : number.ToString();
            case ulong number:
                return number == default(ulong) ? null : number.ToString();
            case float number:
                return number == default(float) ? null : number.ToString();
            case double number:
                return number == default(double) ? null : number.ToString();
            case DateTime date:
                return date == default(DateTime) ? null : date.ToString("s");
            case TimeSpan span:
                return span == default(TimeSpan) ? null : span.ToString();
            case Guid guid:
                return guid == default(Guid) ? null : guid.ToString();
            default:
                return obj?.ToString();
        }
    }
}

Example usage:

var uri = new UriBuilder("test.com")
    .SetQuery(("days", standup.Days), ("time", standup.Time), ("link", standup.Link), ("pizzas", standup.Pizzas))
    .Uri;

Output:
http://test.com/?days=Tuesday&days=Wednesday&time=14:30:00&link=conferences.com%2Fapple%2Fstream%2F15

Sabah answered 23/2, 2019 at 20:45 Comment(0)
M
0

Problem

All solutions provided are either extremely situational, partially situational, or resource intensive due to runtime Type checks, which heavily involve System.Reflection which subsequently is involving the interpretation of code at runtime, rather than compilation. To combat these issues, a query formatting mechanism that is working with any type of objects, any type of nested objects, and any number of nested objects, and any type of propriety, must be created. This solution uses the Newtonsoft.Json Nuget library to process the JSON objects.

Solution

In order to be able to transform any object in an HTTP query string the data to be formatted must be firstly serialised in a JSON object. The JSON serialisation is done to limit the data types to a very limited variation in order to make the data types predictable and easy to format as an HTTP query string. All the implementations shown involve Type checks that are set to work only on a limited set of data types and child proprieties with certain set dimensions (e.g. 1D, 2D, etc.). This approach is also increasing the performance, by using JSON object classes and verifying their JSON data types, thus avoiding the the use of reflection.

Implementation

    public class Query_Formater
    {
        public static async Task<string> ObjectQueryFormatter<Object>(Object? value)
        {
            // STRING BUILDER OBJECT THAT CONTAINS THE FORMATED HTTP QUERY STRING
            StringBuilder query = new StringBuilder();

            // OBJECT TO BE FORMATED AS A HTTP QUERY STRING SERIALISED AS A JSON STRING AND PARSED INTO A "JObject" OBJECT
            JObject json_value = JObject.Parse(Newtonsoft.Json.JsonConvert.SerializeObject(value));


            if (value != null)
            {
                // IF THE VALUE IS NOT NULL EXTRACT THE PARAMETERS OF THE "JObject" OBJECT AND FORMAT THEM IN HTTP QUERY STRING FORMAT
                await Query_Object_Parameters_Extractor(query, json_value, null);
            }

            // REMOVE ALL OBJECTS IN THE "JObject" OBJECT FROM THE MEMORY
            json_value?.RemoveAll();

            // RETURN THE FORMATED HTTP QUERY STRING
            return query.ToString();
        }


        private static async Task<bool> Query_Array_Parameters_Extractor(StringBuilder query, JArray json_value, string name)
        {
            // CREATE A "StringBuilder" TO GENERATE THE PROPRIETY NAME WITHIN IT
            StringBuilder name_builder = new StringBuilder();

            // ENUMERATE EACH PROPRIETY IN THE "JArray"
            for (int i = 0; i < json_value.Count; i++)
            {
                // STORE THE CURRENT PROPRIETY OF THE "JArray" OBJECT
                JToken current_element = json_value.ElementAt(i);

                // BUILD THE ARRAYS CURRENT PROPRIETY NAME

                // IF THE ARRAY IS NOT THE CHILD OF AN OBJECT THE PROPRIETY NAME WILL BE SET BY
                // USING THE ARRAY PROPRIETY NAME IS SET IN THE "Query_Object_Parameters_Extractor"
                // METHOD AND PASSED TO THIS METHOD THROUGH THE "name" PARAMETER.
                // (e.g. arrayValues[0], arrayValues[1] ...)

                // IF THE ARRAY IS THE CHILD OF AN OBJECT THE PARENT TO CHILD RELATION IS SET IN THE
                // "Query_Object_Parameters_Extractor" METHOD AND PASSED TO THIS METHOD THROUGH
                // THE "name" PARAMETER.
                // (e.g. object1.arrayValues[0], object1.arrayValues[1] ...)
                // (e.g. object1.object2.arrayValues[0], object1.object2.arrayValues[1] ...)

                name_builder.Append(name);
                name_builder.Append("[");
                name_builder.Append(i);
                name_builder.Append("]");

                // STORE THE PROCESSED PROPRIETY NAME IN A VARIABLE
                string name_result = name_builder.ToString();

                // CLEAR THE "StringBuilder" OBJECT
                name_builder.Clear();

                // IF THE CURRENT PROPRIETY IS A JSON OBJECT
                if (current_element.Type == JTokenType.Object)
                {
                    // CALL THE "Query_Object_Parameters_Extractor" METHOD AND PASS THE CURRENT OBJECT AS THE
                    // "JObject" OBJECT, THE "query" "StringBuilder" AS A REFERENCE, AND THE "name_result".
                    await Query_Object_Parameters_Extractor(query, (JObject)current_element, name_result);
                }
                // IF THE CURRENT PROPRIETY IS A JSON ARRAY
                else if (current_element.Type == JTokenType.Array)
                {
                    // PERFORM RECURSION TO EXTRACT THE VALUES
                    // FROM THE CURRENT "JArray" PROPRIETY.
                    await Query_Array_Parameters_Extractor(query, (JArray)current_element, name_result);
                }
                else
                {
                    // FORMAT THE NAME OF THE PROPRIETY AND ITS VALUE AND ADD THEM IN THE "query" "StringBuilder"
                    // (e.g. PROPRIETY NAME: Query Test;
                    //       PROPRIETY VALUE: Testing;
                    //       RESULTING VALUE: Query+Test=Testing)

                    query.Append(System.Web.HttpUtility.UrlEncode(name_result));
                    query.Append("=");
                    query.Append(System.Web.HttpUtility.UrlEncode(json_value.ElementAt(i).ToString()));
                }

                // IF THE CURRENT JSON PROPRIETY IS NOT THE LAST ONE, APPEND "&"
                // TO CONCATENATE THE PRECEDING ELEMENTS TO IT
                // (e.g Query+Test=Testing&PRECEDING=1 )
                if (i < json_value.Count - 1)
                {
                    query.Append("&");
                }
            }

            return true;
        }

        private static async Task<bool> Query_Object_Parameters_Extractor(StringBuilder query, JObject json_value, string? name)
        {
            // CREATE A "StringBuilder" TO GENERATE THE PROPRIETY NAME WITHIN IT
            StringBuilder name_builder = new StringBuilder();

            // GET ALL JSON PROPRIETIES OF THE "JObject" OBJECT
            IEnumerable<JProperty> proprieties = json_value.Properties();

            // ENUMERATE THROUGH THE PROPRIETIES OF THE "JObject" OBJECT WITH AN "IEnumerator"
            IEnumerator<JProperty> proprieties_enumerator = proprieties.GetEnumerator();

            try
            {
                // GET THE NUMBER OF PROPRIETIES WITHIN THE "JObject" OBJECT
                int count = proprieties.Count();

                // WHILE A PROPRIETY IS AVAILABLE WITHIN THE "IEnumerator"
                while (proprieties_enumerator.MoveNext() == true)
                {
                    // IF THE METHOD'S "name" PARAMETER IS NOT NULL
                    if (name != null)
                    {
                        // FOR EXAMPLE IF THE CURRENT PROPRIETY IS AN INNER OBJECT,
                        // APPEND THE CHILD OBJECT TO THE PARENT OBJECT NAME
                        // (e.g. PARENT OBJECT: calendarDates;
                        //       CHILD OBJECT: currentDate;
                        //       RESULTING OBJECT NAME: calendarDates.currentDate)

                        name_builder.Append(name);
                        name_builder.Append(".");
                        name_builder.Append(proprieties_enumerator.Current.Name);
                    }
                    else
                    {
                        // IF THE CURRENT PROPRIETY IS NOT AN INNER OBJECT, APPEND ONLY
                        // THE CURRENT PROPRIETY NAME
                        name_builder.Append(proprieties_enumerator.Current.Name);
                    }

                    // STORE THE PROCESSED PROPRIETY NAME IN A VARIABLE
                    string name_result = name_builder.ToString();
                    name_builder.Clear();

                    // IF THE CURRENT PROPRIETY IS A JSON OBJECT
                    if (proprieties_enumerator.Current.Value.Type == JTokenType.Object)
                    {
                        // PERFORM RECURSION AND PASS THE CURRENT OBJECT AS THE "JObject" OBJECT,
                        // THE "query" "StringBuilder" AS A REFERENCE, AND THE "name_result".
                        await Query_Object_Parameters_Extractor(query, (JObject)proprieties_enumerator.Current.Value, name_result);
                    }
                    // IF THE CURRENT PROPRIETY IS A JSON ARRAY
                    else if (proprieties_enumerator.Current.Value.Type == JTokenType.Array)
                    {
                        // CALL THE "Query_Array_Parameters_Extractor" METHOD TO EXTRACT THE VALUES
                        // FROM THE CURRENT "JArray" PROPRIETY.
                        await Query_Array_Parameters_Extractor(query, (JArray)proprieties_enumerator.Current.Value, name_result);
                    }
                    else
                    {
                        // FORMAT THE NAME OF THE PROPRIETY AND ITS VALUE AND ADD THEM IN THE "query" "StringBuilder"
                        // (e.g. PROPRIETY NAME: Query Test;
                        //       PROPRIETY VALUE: Testing;
                        //       RESULTING VALUE: Query+Test=Testing)

                        query.Append(System.Web.HttpUtility.UrlEncode(name_result));
                        query.Append("=");
                        query.Append(System.Web.HttpUtility.UrlEncode(proprieties_enumerator.Current.Value.ToString()));
                    }


                    // IF THE CURRENT JSON PROPRIETY IS NOT THE LAST ONE, APPEND "&"
                    // TO CONCATENATE THE PRECEDING ELEMENTS TO IT
                    // (e.g Query+Test=Testing&PRECEDING=1 )
                    if (count > 1)
                    {
                        query.Append("&");
                    }

                    // DECREMENT THE ELEMENT COUNT BY ONE AT EACH ITERATION
                    count--;
                }
            }
            catch
            {

            }
            finally
            {
                // DEALLOCATE THE "proprieties_enumerator" OBJECT FROM MEMORY
                proprieties_enumerator?.Dispose();
            }

            return true;
        }
    }

The implementation has a best time complexity of O(N), if the root proprieties are not objects or lists. The implementation has an average and worst time complexity of O((N * TCO) + CP), where N equals with the number of proprieties in the root object, TCO equals with the total number of child objects, and TCP equals with the total number of child proprieties.

Testing the implementation

Test object with complex structure

The parameters of the test object are set as non-nullable in order for the API to throw errors if any parameter was not serialised properly as an HTTP query string.

    public class InnerInnerTestObject
    {
        public string sTring { get; set; }
        public List<string> t { get; set; }
    }

    public class InnerTestObject
    {
        public string sTring { get; set; }
        public List<InnerInnerTestObject> t { get; set; }
    }

    public class TrasnsistiveObject
    {
        public List<InnerTestObject> t { get; set; }
    }
    public class TestObject
    {
        public string? s { get; set; }

        public TrasnsistiveObject trasnsistiveObject { get; set; }

        public List<InnerTestObject> t { get; set; }
    }

Value assignment of the test object

       TestObject obj = new TestObject();
       obj.s = "Hall test string";
       obj.t = new List<InnerTestObject>()
       {
           new InnerTestObject()
           {
               sTring = "Testing query formater",
               t = new List<InnerInnerTestObject>()
               {
                   new InnerInnerTestObject()
                   {
                       sTring = "Testing query formater",
                       t = new List<string>()
                       {
                           "query formater test 1",
                           "query formater test 2",
                           "query formater test 3"
                       }
                   },
                   new InnerInnerTestObject()
                   {
                       sTring = "Testing query formater",
                       t = new List<string>()
                       {
                           "query formater test 4",
                           "query formater test 5",
                           "query formater test 6"
                       }
                   },
                    new InnerInnerTestObject()
                   {
                       sTring = "Testing query formater",
                       t = new List<string>()
                       {
                           "query formater test 7",
                           "query formater test 8",
                           "query formater test 9"
                       }
                   }
               }
           },
           new InnerTestObject()
           {
               sTring = "Testing query formater",
                t = new List<InnerInnerTestObject>()
               {
                   new InnerInnerTestObject()
                   {
                       sTring = "Testing query formater",
                       t = new List<string>()
                       {
                           "query formater test 10",
                           "query formater test 11",
                           "query formater test 12"
                       }
                   },
                   new InnerInnerTestObject()
                   {
                       sTring = "Testing query formater",
                       t = new List<string>()
                       {
                           "query formater test 13",
                           "query formater test 14",
                           "query formater test 15"
                       }
                   },
                    new InnerInnerTestObject()
                   {
                       sTring = "Testing query formater",
                       t = new List<string>()
                       {
                           "query formater test 16",
                           "query formater test 17",
                           "query formater test 18"
                       }
                   }
               }
           }
       };
       obj.trasnsistiveObject = new TrasnsistiveObject()
       {
           t = new List<InnerTestObject>()
           {
               new InnerTestObject()
               {
                   sTring = "Testing transistive query formater",
               t = new List<InnerInnerTestObject>()
               {
                   new InnerInnerTestObject()
                   {
                       sTring = "Testing query formater",
                       t = new List<string>()
                       {
                           "query formater test 1",
                           "query formater test 2",
                           "query formater test 3"
                       }
                   },
                   new InnerInnerTestObject()
                   {
                       sTring = "Testing query formater",
                       t = new List<string>()
                       {
                           "query formater test 4",
                           "query formater test 5",
                           "query formater test 6"
                       }
                   },
                    new InnerInnerTestObject()
                   {
                       sTring = "Testing query formater",
                       t = new List<string>()
                       {
                           "query formater test 7",
                           "query formater test 8",
                           "query formater test 9"
                       }
                   }
               }
               },
               new InnerTestObject()
               {
                   sTring = "Testing transistive query formater",
               t = new List<InnerInnerTestObject>()
               {
                   new InnerInnerTestObject()
                   {
                       sTring = "Testing query formater",
                       t = new List<string>()
                       {
                           "query formater test 100",
                           "query formater test 200",
                           "query formater test 300"
                       }
                   },
                   new InnerInnerTestObject()
                   {
                       sTring = "Testing query formater",
                       t = new List<string>()
                       {
                           "query formater test 400",
                           "query formater test 500",
                           "query formater test 600"
                       }
                   },
                    new InnerInnerTestObject()
                   {
                       sTring = "Testing query formater",
                       t = new List<string>()
                       {
                           "query formater test 700",
                           "query formater test 800",
                           "query formater test 900"
                       }
                   }
               }
               }
           }
       };

Query transmission to the API endpoint

        // FORMAT THE OBJECT INTO A QUERY STRING AND SEND IT TO THE API ENDPOINT
        System.Net.Http.HttpClient client = new HttpClient();
        client.BaseAddress = new Uri(Navigation_Manager.BaseUri);
       
        HttpResponseMessage m = await client.GetAsync("auth/get-account?" + (await Query_Formater.ObjectQueryFormatter<TestObject>(obj)));

        // READ THE API ENDPOINT RESPONSE 
        Console.WriteLine(await m.Content.ReadAsStringAsync());

Query retrieval at API Endpoint

        [HttpGet("get-account")]
        public Task<ActionResult<string?>> Get([FromQuery] TestObject? data)
        {
            // SERIALISE THE RECEIVED QUERY OBJECT INTO JSON STRING TO READ THE DATA MORE EASILY
            Console.WriteLine("Result:\n" + Newtonsoft.Json.JsonConvert.SerializeObject(data, Newtonsoft.Json.Formatting.Indented));
            return Task.FromResult<ActionResult<string?>>(Content("OK Get"));
        }

Live server generated response

{
  "s": "Hall test string",
  "trasnsistiveObject": {
    "t": [
      {
        "sTring": "Testing transistive query formater",
        "t": [
          {
            "sTring": "Testing query formater",
            "t": [
              "query formater test 1",
              "query formater test 2",
              "query formater test 3"
            ]
          },
          {
            "sTring": "Testing query formater",
            "t": [
              "query formater test 4",
              "query formater test 5",
              "query formater test 6"
            ]
          },
          {
            "sTring": "Testing query formater",
            "t": [
              "query formater test 7",
              "query formater test 8",
              "query formater test 9"
            ]
          }
        ]
      },
      {
        "sTring": "Testing transistive query formater",
        "t": [
          {
            "sTring": "Testing query formater",
            "t": [
              "query formater test 100",
              "query formater test 200",
              "query formater test 300"
            ]
          },
          {
            "sTring": "Testing query formater",
            "t": [
              "query formater test 400",
              "query formater test 500",
              "query formater test 600"
            ]
          },
          {
            "sTring": "Testing query formater",
            "t": [
              "query formater test 700",
              "query formater test 800",
              "query formater test 900"
            ]
          }
        ]
      }
    ]
  },
  "t": [
    {
      "sTring": "Testing query formater",
      "t": [
        {
          "sTring": "Testing query formater",
          "t": [
            "query formater test 1",
            "query formater test 2",
            "query formater test 3"
          ]
        },
        {
          "sTring": "Testing query formater",
          "t": [
            "query formater test 4",
            "query formater test 5",
            "query formater test 6"
          ]
        },
        {
          "sTring": "Testing query formater",
          "t": [
            "query formater test 7",
            "query formater test 8",
            "query formater test 9"
          ]
        }
      ]
    },
    {
      "sTring": "Testing query formater",
      "t": [
        {
          "sTring": "Testing query formater",
          "t": [
            "query formater test 10",
            "query formater test 11",
            "query formater test 12"
          ]
        },
        {
          "sTring": "Testing query formater",
          "t": [
            "query formater test 13",
            "query formater test 14",
            "query formater test 15"
          ]
        },
        {
          "sTring": "Testing query formater",
          "t": [
            "query formater test 16",
            "query formater test 17",
            "query formater test 18"
          ]
        }
      ]
    }
  ]
}

Live result picture 1

Live result picture 2

Live result picture 3

Live result picture 4

Mai answered 3/1 at 0:19 Comment(0)
R
-3

Faced with a similar situation what I did, is to XML serialize the object and pass it around as query string parameter. The difficulty with this approach was that despite encoding, the receiving form throws exception saying "potentially dangerous request...". The way I got around was to encrypt the serialized object and then encode to pass it around as query string parameter. Which in turn made the query string tamper proof (bonus wandering into the HMAC territory)!

FormA XML serializes an object > encrypts the serialized string > encode > pass as query string to FormB FormB decrypts the query parameter value (as request.querystring decodes also) > deserialize the resulting XML string to object using XmlSerializer.

I can share my VB.NET code upon request to howIdidit-at-applecart-dot-net

Rosalia answered 2/3, 2013 at 16:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.