如何从嵌套对象生成带有扁平化参数名称的查询字符串

huangapple go评论76阅读模式
英文:

How to generate query string with flattened param name from a nested object

问题

我想将我的嵌套请求转换为查询字符串,类似于以下内容:

> 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解决方法

我尝试使用*WebSerializer*库调用`WebSerializer.ToQueryString(parameter)`。我看到以下输出:

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

如您所见,输出结果与预期不符,我的ASP.NET Web API .NET6示例服务器无法接受此格式。(有关更多信息,请参见 https://github.com/BSVN/IpTables.Api/pull/18)

您是否了解其他库可以完成此操作,或者是否有一些技巧可以修复使用*WebSerializer*的问题?

<details>
<summary>英文:</summary>

I want to convert my below nested request to Query String

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

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 the WebSerializer library with call WebSerializer.ToQueryString(parameter). I see the 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 Web API .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?

答案1

得分: 1

尝试这个:

    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)}"));
英文:

try this :

static List&lt;(string Key, string Value)&gt; SerializeObject(object obj, string parent) 
{
    var result = new List&lt;(string Key, string Value)&gt;();
    foreach (var p in obj.GetType().GetProperties().Where(p =&gt; p.CanRead))
    {
        var v = p.GetValue(obj);
        if (v is null) continue;
        var pp = $&quot;{(!string.IsNullOrEmpty(parent) ? $&quot;{parent}.&quot; : &quot;&quot;)}{p.Name}&quot;;
    
        if (CanSeriazlieToString(p)) result.Add(new (pp, v!.ToString()!));
        else result.AddRange(SerializeObject(v, $&quot;{pp}&quot;));
    }
    return result;
}
static bool CanSeriazlieToString(PropertyInfo pInfo)
{
    return (pInfo.PropertyType == typeof(string) || pInfo.PropertyType == typeof(Guid) || pInfo.PropertyType.IsPrimitive);
}
var queryString = string.Join(&quot;&amp;&quot;, SerializeObject(parameter, string.Empty).Select(x =&gt; $&quot;{x.Key}={HttpUtility.UrlEncode(x.Value)}&quot;));

答案2

得分: 1

以下是翻译好的内容:

如果你寻找的答案不是使用`WebSerializer`方法,

*实现`DataContract`属性似乎是最快的方法,但我强烈怀疑下次如果你只想将`RuleInputModel`实例转换为查询字符串,或者如果你有其他具有`RuleInputModel`类型属性(但名称不同)的模型,结果将导致查询参数名称将有"Data."前缀。*

因此,建议为此编写自定义实现。在这种情况下,预期结果是扁平化的JSON,然后转换为查询字符串。

您可以使用*Newtonsoft.Json*库来实现JSON扁平化,将对象转换为`Dictionary&lt;string, object&gt;`类型,感谢[GuruStron关于使用c#通用地扁平化Json的回答][1]。

第二种方法是将`Dictionary&lt;string, object&gt;`转换为查询字符串。

```csharp
using System.Web;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public static class Helpers
{
	public static Dictionary&lt;string, object&gt; FlattenObject&lt;T&gt;(T source)
		where T : class, new()
		{
			return JObject.FromObject(source)
				.Descendants()
				.OfType&lt;JValue&gt;()
				.ToDictionary(jv =&gt; jv.Path, jv =&gt; jv.Value&lt;object&gt;());
		}
	
	public static string ToQueryString(this Dictionary&lt;string, object&gt; dict)
	{
		StringBuilder sb = new StringBuilder();
		
		foreach (var kvp in dict)
		{
			sb.Append(&quot;&amp;&quot;)
				.Append($&quot;{kvp.Key}={HttpUtility.UrlEncode(kvp.Value.ToString())}&quot;);
		}
		
		return sb.ToString()
			.Trim(&#39;&amp;&#39;);
	}
}

在您的RulesCommandServiceAppendRequest类中进行的小更改,您希望将Chain值序列化为字符串而不是整数。

using Newtonsoft.Json.Converters;

public class RulesCommandServiceAppendRequest
{
	[JsonConverter(typeof(StringEnumConverter))]
	public Chain Chain { get; set; }
	
	public RuleInputModel Data { get; set; }
}

调用方法:

Helpers.FlattenObject(parameter)
		.ToQueryString();

<details>
<summary>英文:</summary>

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 &quot;Data.&quot; 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&lt;string, object&gt;` type, credit to the [GuruStron&#39;s answer on Generically Flatten Json using c#][1]. 

For the second method will be converting the `Dictionary&lt;string, object&gt;` to query string.

```csharp
using System.Web;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

public static class Helpers
{
	public static Dictionary&lt;string, object&gt; FlattenObject&lt;T&gt;(T source)
		where T : class, new()
		{
			return JObject.FromObject(source)
				.Descendants()
				.OfType&lt;JValue&gt;()
				.ToDictionary(jv =&gt; jv.Path, jv =&gt; jv.Value&lt;object&gt;());
		}
	
	public static string ToQueryString(this Dictionary&lt;string, object&gt; dict)
	{
		StringBuilder sb = new StringBuilder();
		
		foreach (var kvp in dict)
		{
			sb.Append(&quot;&amp;&quot;)
				.Append($&quot;{kvp.Key}={HttpUtility.UrlEncode(kvp.Value.ToString())}&quot;);
		}
		
		return sb.ToString()
			.Trim(&#39;&amp;&#39;);
	}
}

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();

答案3

得分: 1

我使用 WebSerializerAttribute 成功使其工作:

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

英文:

I got it to work using WebSerializerAttribute:

using System;
using Cysharp.Web;
					
public class Program
{
	public static void Main()
	{
		var parameter = new RulesCommandServiceAppendRequest()
            {
                Chain = Chain.INPUT,
                Data = new RuleInputModel()
                {
                    Protocol = &quot;tcp&quot;,
                    SourceIp = &quot;2.2.2.2&quot;,
                    DestinationIp = &quot;1.1.1.1&quot;,
                    SourcePort = &quot;111&quot;,
                    DestinationPort = &quot;222&quot;,
                    Jump = &quot;DROP&quot;
                }
            };
		
		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&lt;RuleInputModel&gt;
{
	public void Serialize(ref WebSerializerWriter writer, RuleInputModel value, WebSerializerOptions options)
    {
	    var prefix = writer.NamePrefix;
        writer.NamePrefix = &quot;Data.&quot;;
		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

答案4

得分: 0

如果您希望查询字符串参数包括嵌套对象属性的“Data”前缀,请尝试以下代码:

class Program
{
    static void Main(string[] args)
    {
        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"
            }
        };

        string queryString = ConvertToQueryString("Data", parameter);
        Console.WriteLine(queryString);
    }

    static string ConvertToQueryString(string prefix, object obj)
    {
        var properties = obj.GetType().GetProperties();
        var queryStringParams = new List<string>();

        foreach (var property in properties)
        {
            var value = property.GetValue(obj);
            if (value != null)
            {
                var propertyName = $"{prefix}.{property.Name}";
                if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
                {
                    queryStringParams.Add(ConvertToQueryString(propertyName, value));
                }
                else
                {
                    queryStringParams.Add($"{propertyName}={Uri.EscapeDataString(value.ToString())}");
                }
            }
        }

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

希望这有所帮助。

英文:

If you want the query string parameters to include the "Data" prefix for the nested object properties, then try this:

class Program
{
    static void Main(string[] args)
    {
        var parameter = new RulesCommandServiceAppendRequest()
        {
            Chain = Chain.INPUT,
            Data = new RuleInputModel()
            {
                Protocol = &quot;tcp&quot;,
                SourceIp = &quot;2.2.2.2&quot;,
                DestinationIp = &quot;1.1.1.1&quot;,
                SourcePort = &quot;111&quot;,
                DestinationPort = &quot;222&quot;,
                Jump = &quot;DROP&quot;
            }
        };

        string queryString = ConvertToQueryString(&quot;Data&quot;, parameter);
        Console.WriteLine(queryString);
    }

    static string ConvertToQueryString(string prefix, object obj)
    {
        var properties = obj.GetType().GetProperties();
        var queryStringParams = new List&lt;string&gt;();

        foreach (var property in properties)
        {
            var value = property.GetValue(obj);
            if (value != null)
            {
                var propertyName = $&quot;{prefix}.{property.Name}&quot;;
                if (property.PropertyType.IsClass &amp;&amp; property.PropertyType != typeof(string))
                {
                    queryStringParams.Add(ConvertToQueryString(propertyName, value));
                }
                else
                {
                    queryStringParams.Add($&quot;{propertyName}={Uri.EscapeDataString(value.ToString())}&quot;);
                }
            }
        }

        return string.Join(&quot;&amp;&quot;, queryStringParams);
    }
}

huangapple
  • 本文由 发表于 2023年8月10日 15:52:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/76873657.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定