skip to Main Content

I want to convert my below nested request to Query String

            var parameter = new RulesCommandServiceAppendRequest()
            {
                Chain = Chain.INPUT,
                Data = new RuleInputModel()
                {
                    Protocol = "tcp",
                    SourceIp = "2.2.2.2",
                    DestinationIp = "1.1.1.1",
                    SourcePort = "111",
                    DestinationPort = "222",
                    Jump = "DROP"
                }
            };

to something like below

Chain=INPUT&Data.DestinationIp=1.1.1.1&Data.DestinationPort=222&Data.Jump=DROP&Data.Protocol=tcp&Data.SourceIp=2.2.2.2&Data.SourcePort=111

WebSerializer workaround

I try with WebSerializer library with call WebSerializer.ToQueryString(parameter) I see below output:

Chain=INPUT&Data=DestinationIp=1.1.1.1&DestinationPort=222&Jump=DROP&Protocol=tcp&SourceIp=2.2.2.2&SourcePort=111"

As you can see, the output is not as expected and my ASP.NET WebApi .NET6 sample server does not accept this. (for more info you can see https://github.com/BSVN/IpTables.Api/pull/18)

Did you know another library for doing this or some trick to correct using WebSerializer?

3

Answers


  1. try this :

    static List<(string Key, string Value)> SerializeObject(object obj, string parent) 
    {
        var result = new List<(string Key, string Value)>();
        foreach (var p in obj.GetType().GetProperties().Where(p => p.CanRead))
        {
            var v = p.GetValue(obj);
            if (v is null) continue;
            var pp = $"{(!string.IsNullOrEmpty(parent) ? $"{parent}." : "")}{p.Name}";
        
            if (CanSeriazlieToString(p)) result.Add(new (pp, v!.ToString()!));
            else result.AddRange(SerializeObject(v, $"{pp}"));
        }
        return result;
    }
    static bool CanSeriazlieToString(PropertyInfo pInfo)
    {
        return (pInfo.PropertyType == typeof(string) || pInfo.PropertyType == typeof(Guid) || pInfo.PropertyType.IsPrimitive);
    }
    var queryString = string.Join("&", SerializeObject(parameter, string.Empty).Select(x => $"{x.Key}={HttpUtility.UrlEncode(x.Value)}"));
    
    Login or Signup to reply.
  2. Well, if you look for the answer other than using the WebSerializer way,

    Implement the DataContract attribute seems the quickest, but I highly doubt next time if you want to convert the RuleInputModel instance only to query string or if you have other models with the property with RuleInputModel type (but different name), it will result in the query param name will have the "Data." prefix.

    Thus, recommend writing a custom implementation for it. For this case, the expected result is flattened JSON and then converted to a query string.

    You can use the Newtonsoft.Json library to achieve the JSON flattening, converting an object to Dictionary<string, object> type, credit to the GuruStron’s answer on Generically Flatten Json using c#.

    For the second method will be converting the Dictionary<string, object> to query string.

    using System.Web;
    using System.Text;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    
    public static class Helpers
    {
        public static Dictionary<string, object> FlattenObject<T>(T source)
            where T : class, new()
            {
                return JObject.FromObject(source)
                    .Descendants()
                    .OfType<JValue>()
                    .ToDictionary(jv => jv.Path, jv => jv.Value<object>());
            }
        
        public static string ToQueryString(this Dictionary<string, object> dict)
        {
            StringBuilder sb = new StringBuilder();
            
            foreach (var kvp in dict)
            {
                sb.Append("&")
                    .Append($"{kvp.Key}={HttpUtility.UrlEncode(kvp.Value.ToString())}");
            }
            
            return sb.ToString()
                .Trim('&');
        }
    }
    

    The minor change in your RulesCommandServiceAppendRequest class which you want to serialize the Chain value as string instead of integer.

    using Newtonsoft.Json.Converters;
    
    public class RulesCommandServiceAppendRequest
    {
        [JsonConverter(typeof(StringEnumConverter))]
        public Chain Chain { get; set; }
        
        public RuleInputModel Data { get; set; }
    }
    

    Caller method:

    Helpers.FlattenObject(parameter)
            .ToQueryString();
    
    Login or Signup to reply.
  3. I got it to work using WebSerializerAnnotation:

    using System;
    using Cysharp.Web;
                        
    public class Program
    {
        public static void Main()
        {
            var parameter = new RulesCommandServiceAppendRequest()
                {
                    Chain = Chain.INPUT,
                    Data = new RuleInputModel()
                    {
                        Protocol = "tcp",
                        SourceIp = "2.2.2.2",
                        DestinationIp = "1.1.1.1",
                        SourcePort = "111",
                        DestinationPort = "222",
                        Jump = "DROP"
                    }
                };
            
            WebSerializer.ToQueryString(parameter).Dump();
        }
    }
    
    public enum Chain
    {
        INPUT
    }
    
    public class RulesCommandServiceAppendRequest
    {
        public Chain Chain {get; set;}
    
        [WebSerializer(typeof(RuleInputModelSerializer))]
        public RuleInputModel Data {get; set;}
    }
    
    public class RuleInputModel
    {
        public string Protocol {get; set;}
        public string SourceIp {get; set;}
        public string DestinationIp {get; set;}
        public string SourcePort {get; set;}
        public string DestinationPort {get; set;}
        public string Jump {get; set;}
    }
    
    public class RuleInputModelSerializer : IWebSerializer<RuleInputModel>
    {
        public void Serialize(ref WebSerializerWriter writer, RuleInputModel value, WebSerializerOptions options)
        {
            var prefix = writer.NamePrefix;
            writer.NamePrefix = "Data.";
            WebSerializer.ToQueryString(writer, value, options);
            writer.NamePrefix = prefix;
        }
    }
    

    Fiddle : https://dotnetfiddle.net/h28gKx

    Output:

    Chain=INPUT&Data=Data.DestinationIp=1.1.1.1&Data.DestinationPort=222&Data.Jump=DROP&Data.Protocol=tcp&Data.SourceIp=2.2.2.2&Data.SourcePort=111

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search