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

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

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

问题

  1. 我想将我的嵌套请求转换为查询字符串,类似于以下内容:
  2. > 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
  3. WebSerializer解决方法
  4. 我尝试使用*WebSerializer*库调用`WebSerializer.ToQueryString(parameter)`。我看到以下输出:
  5. > Chain=INPUT&Data=DestinationIp=1.1.1.1&DestinationPort=222&Jump=DROP&Protocol=tcp&SourceIp=2.2.2.2&SourcePort=111"
  6. 如您所见,输出结果与预期不符,我的ASP.NET Web API .NET6示例服务器无法接受此格式。(有关更多信息,请参见 https://github.com/BSVN/IpTables.Api/pull/18)
  7. 您是否了解其他库可以完成此操作,或者是否有一些技巧可以修复使用*WebSerializer*的问题?
  8. <details>
  9. <summary>英文:</summary>
  10. I want to convert my below nested request to Query String
  11. ```csharp
  12. var parameter = new RulesCommandServiceAppendRequest()
  13. {
  14. Chain = Chain.INPUT,
  15. Data = new RuleInputModel()
  16. {
  17. Protocol = &quot;tcp&quot;,
  18. SourceIp = &quot;2.2.2.2&quot;,
  19. DestinationIp = &quot;1.1.1.1&quot;,
  20. SourcePort = &quot;111&quot;,
  21. DestinationPort = &quot;222&quot;,
  22. Jump = &quot;DROP&quot;
  23. }
  24. };

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

  1. 尝试这个:
  2. static List<(string Key, string Value)> SerializeObject(object obj, string parent)
  3. {
  4. var result = new List<(string Key, string Value)>();
  5. foreach (var p in obj.GetType().GetProperties().Where(p => p.CanRead))
  6. {
  7. var v = p.GetValue(obj);
  8. if (v is null) continue;
  9. var pp = $"{(!string.IsNullOrEmpty(parent) ? $"{parent}." : "")}{p.Name}";
  10. if (CanSeriazlieToString(p)) result.Add(new (pp, v!.ToString()!));
  11. else result.AddRange(SerializeObject(v, $"{pp}"));
  12. }
  13. return result;
  14. }
  15. static bool CanSeriazlieToString(PropertyInfo pInfo)
  16. {
  17. return (pInfo.PropertyType == typeof(string) || pInfo.PropertyType == typeof(Guid) || pInfo.PropertyType.IsPrimitive);
  18. }
  19. var queryString = string.Join("&", SerializeObject(parameter, string.Empty).Select(x => $"{x.Key}={HttpUtility.UrlEncode(x.Value)}"));
英文:

try this :

  1. static List&lt;(string Key, string Value)&gt; SerializeObject(object obj, string parent)
  2. {
  3. var result = new List&lt;(string Key, string Value)&gt;();
  4. foreach (var p in obj.GetType().GetProperties().Where(p =&gt; p.CanRead))
  5. {
  6. var v = p.GetValue(obj);
  7. if (v is null) continue;
  8. var pp = $&quot;{(!string.IsNullOrEmpty(parent) ? $&quot;{parent}.&quot; : &quot;&quot;)}{p.Name}&quot;;
  9. if (CanSeriazlieToString(p)) result.Add(new (pp, v!.ToString()!));
  10. else result.AddRange(SerializeObject(v, $&quot;{pp}&quot;));
  11. }
  12. return result;
  13. }
  14. static bool CanSeriazlieToString(PropertyInfo pInfo)
  15. {
  16. return (pInfo.PropertyType == typeof(string) || pInfo.PropertyType == typeof(Guid) || pInfo.PropertyType.IsPrimitive);
  17. }
  18. var queryString = string.Join(&quot;&amp;&quot;, SerializeObject(parameter, string.Empty).Select(x =&gt; $&quot;{x.Key}={HttpUtility.UrlEncode(x.Value)}&quot;));

答案2

得分: 1

以下是翻译好的内容:

  1. 如果你寻找的答案不是使用`WebSerializer`方法,
  2. *实现`DataContract`属性似乎是最快的方法,但我强烈怀疑下次如果你只想将`RuleInputModel`实例转换为查询字符串,或者如果你有其他具有`RuleInputModel`类型属性(但名称不同)的模型,结果将导致查询参数名称将有"Data."前缀。*
  3. 因此,建议为此编写自定义实现。在这种情况下,预期结果是扁平化的JSON,然后转换为查询字符串。
  4. 您可以使用*Newtonsoft.Json*库来实现JSON扁平化,将对象转换为`Dictionary&lt;string, object&gt;`类型,感谢[GuruStron关于使用c#通用地扁平化Json的回答][1]。
  5. 第二种方法是将`Dictionary&lt;string, object&gt;`转换为查询字符串。
  6. ```csharp
  7. using System.Web;
  8. using System.Text;
  9. using Newtonsoft.Json;
  10. using Newtonsoft.Json.Linq;
  11. public static class Helpers
  12. {
  13. public static Dictionary&lt;string, object&gt; FlattenObject&lt;T&gt;(T source)
  14. where T : class, new()
  15. {
  16. return JObject.FromObject(source)
  17. .Descendants()
  18. .OfType&lt;JValue&gt;()
  19. .ToDictionary(jv =&gt; jv.Path, jv =&gt; jv.Value&lt;object&gt;());
  20. }
  21. public static string ToQueryString(this Dictionary&lt;string, object&gt; dict)
  22. {
  23. StringBuilder sb = new StringBuilder();
  24. foreach (var kvp in dict)
  25. {
  26. sb.Append(&quot;&amp;&quot;)
  27. .Append($&quot;{kvp.Key}={HttpUtility.UrlEncode(kvp.Value.ToString())}&quot;);
  28. }
  29. return sb.ToString()
  30. .Trim(&#39;&amp;&#39;);
  31. }
  32. }

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

  1. using Newtonsoft.Json.Converters;
  2. public class RulesCommandServiceAppendRequest
  3. {
  4. [JsonConverter(typeof(StringEnumConverter))]
  5. public Chain Chain { get; set; }
  6. public RuleInputModel Data { get; set; }
  7. }

调用方法:

  1. Helpers.FlattenObject(parameter)
  2. .ToQueryString();
  1. <details>
  2. <summary>英文:</summary>
  3. Well, if you look for the answer other than using the `WebSerializer` way,
  4. *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.*
  5. Thus, recommend writing a custom implementation for it. For this case, the expected result is flattened JSON and then converted to a query string.
  6. 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].
  7. For the second method will be converting the `Dictionary&lt;string, object&gt;` to query string.
  8. ```csharp
  9. using System.Web;
  10. using System.Text;
  11. using Newtonsoft.Json;
  12. using Newtonsoft.Json.Linq;
  13. public static class Helpers
  14. {
  15. public static Dictionary&lt;string, object&gt; FlattenObject&lt;T&gt;(T source)
  16. where T : class, new()
  17. {
  18. return JObject.FromObject(source)
  19. .Descendants()
  20. .OfType&lt;JValue&gt;()
  21. .ToDictionary(jv =&gt; jv.Path, jv =&gt; jv.Value&lt;object&gt;());
  22. }
  23. public static string ToQueryString(this Dictionary&lt;string, object&gt; dict)
  24. {
  25. StringBuilder sb = new StringBuilder();
  26. foreach (var kvp in dict)
  27. {
  28. sb.Append(&quot;&amp;&quot;)
  29. .Append($&quot;{kvp.Key}={HttpUtility.UrlEncode(kvp.Value.ToString())}&quot;);
  30. }
  31. return sb.ToString()
  32. .Trim(&#39;&amp;&#39;);
  33. }
  34. }

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

  1. using Newtonsoft.Json.Converters;
  2. public class RulesCommandServiceAppendRequest
  3. {
  4. [JsonConverter(typeof(StringEnumConverter))]
  5. public Chain Chain { get; set; }
  6. public RuleInputModel Data { get; set; }
  7. }

Caller method:

  1. Helpers.FlattenObject(parameter)
  2. .ToQueryString();

答案3

得分: 1

我使用 WebSerializerAttribute 成功使其工作:

  1. using System;
  2. using Cysharp.Web;
  3. public class Program
  4. {
  5. public static void Main()
  6. {
  7. var parameter = new RulesCommandServiceAppendRequest()
  8. {
  9. Chain = Chain.INPUT,
  10. Data = new RuleInputModel()
  11. {
  12. Protocol = "tcp",
  13. SourceIp = "2.2.2.2",
  14. DestinationIp = "1.1.1.1",
  15. SourcePort = "111",
  16. DestinationPort = "222",
  17. Jump = "DROP"
  18. }
  19. };
  20. WebSerializer.ToQueryString(parameter).Dump();
  21. }
  22. }
  23. public enum Chain
  24. {
  25. INPUT
  26. }
  27. public class RulesCommandServiceAppendRequest
  28. {
  29. public Chain Chain { get; set; }
  30. [WebSerializer(typeof(RuleInputModelSerializer))]
  31. public RuleInputModel Data { get; set; }
  32. }
  33. public class RuleInputModel
  34. {
  35. public string Protocol { get; set; }
  36. public string SourceIp { get; set; }
  37. public string DestinationIp { get; set; }
  38. public string SourcePort { get; set; }
  39. public string DestinationPort { get; set; }
  40. public string Jump { get; set; }
  41. }
  42. public class RuleInputModelSerializer : IWebSerializer<RuleInputModel>
  43. {
  44. public void Serialize(ref WebSerializerWriter writer, RuleInputModel value, WebSerializerOptions options)
  45. {
  46. var prefix = writer.NamePrefix;
  47. writer.NamePrefix = "Data.";
  48. WebSerializer.ToQueryString(writer, value, options);
  49. writer.NamePrefix = prefix;
  50. }
  51. }

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:

  1. using System;
  2. using Cysharp.Web;
  3. public class Program
  4. {
  5. public static void Main()
  6. {
  7. var parameter = new RulesCommandServiceAppendRequest()
  8. {
  9. Chain = Chain.INPUT,
  10. Data = new RuleInputModel()
  11. {
  12. Protocol = &quot;tcp&quot;,
  13. SourceIp = &quot;2.2.2.2&quot;,
  14. DestinationIp = &quot;1.1.1.1&quot;,
  15. SourcePort = &quot;111&quot;,
  16. DestinationPort = &quot;222&quot;,
  17. Jump = &quot;DROP&quot;
  18. }
  19. };
  20. WebSerializer.ToQueryString(parameter).Dump();
  21. }
  22. }
  23. public enum Chain
  24. {
  25. INPUT
  26. }
  27. public class RulesCommandServiceAppendRequest
  28. {
  29. public Chain Chain {get; set;}
  30. [WebSerializer(typeof(RuleInputModelSerializer))]
  31. public RuleInputModel Data {get; set;}
  32. }
  33. public class RuleInputModel
  34. {
  35. public string Protocol {get; set;}
  36. public string SourceIp {get; set;}
  37. public string DestinationIp {get; set;}
  38. public string SourcePort {get; set;}
  39. public string DestinationPort {get; set;}
  40. public string Jump {get; set;}
  41. }
  42. public class RuleInputModelSerializer : IWebSerializer&lt;RuleInputModel&gt;
  43. {
  44. public void Serialize(ref WebSerializerWriter writer, RuleInputModel value, WebSerializerOptions options)
  45. {
  46. var prefix = writer.NamePrefix;
  47. writer.NamePrefix = &quot;Data.&quot;;
  48. WebSerializer.ToQueryString(writer, value, options);
  49. writer.NamePrefix = prefix;
  50. }
  51. }

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”前缀,请尝试以下代码:

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. var parameter = new RulesCommandServiceAppendRequest()
  6. {
  7. Chain = Chain.INPUT,
  8. Data = new RuleInputModel()
  9. {
  10. Protocol = "tcp",
  11. SourceIp = "2.2.2.2",
  12. DestinationIp = "1.1.1.1",
  13. SourcePort = "111",
  14. DestinationPort = "222",
  15. Jump = "DROP"
  16. }
  17. };
  18. string queryString = ConvertToQueryString("Data", parameter);
  19. Console.WriteLine(queryString);
  20. }
  21. static string ConvertToQueryString(string prefix, object obj)
  22. {
  23. var properties = obj.GetType().GetProperties();
  24. var queryStringParams = new List<string>();
  25. foreach (var property in properties)
  26. {
  27. var value = property.GetValue(obj);
  28. if (value != null)
  29. {
  30. var propertyName = $"{prefix}.{property.Name}";
  31. if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
  32. {
  33. queryStringParams.Add(ConvertToQueryString(propertyName, value));
  34. }
  35. else
  36. {
  37. queryStringParams.Add($"{propertyName}={Uri.EscapeDataString(value.ToString())}");
  38. }
  39. }
  40. }
  41. return string.Join("&", queryStringParams);
  42. }
  43. }

希望这有所帮助。

英文:

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

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. var parameter = new RulesCommandServiceAppendRequest()
  6. {
  7. Chain = Chain.INPUT,
  8. Data = new RuleInputModel()
  9. {
  10. Protocol = &quot;tcp&quot;,
  11. SourceIp = &quot;2.2.2.2&quot;,
  12. DestinationIp = &quot;1.1.1.1&quot;,
  13. SourcePort = &quot;111&quot;,
  14. DestinationPort = &quot;222&quot;,
  15. Jump = &quot;DROP&quot;
  16. }
  17. };
  18. string queryString = ConvertToQueryString(&quot;Data&quot;, parameter);
  19. Console.WriteLine(queryString);
  20. }
  21. static string ConvertToQueryString(string prefix, object obj)
  22. {
  23. var properties = obj.GetType().GetProperties();
  24. var queryStringParams = new List&lt;string&gt;();
  25. foreach (var property in properties)
  26. {
  27. var value = property.GetValue(obj);
  28. if (value != null)
  29. {
  30. var propertyName = $&quot;{prefix}.{property.Name}&quot;;
  31. if (property.PropertyType.IsClass &amp;&amp; property.PropertyType != typeof(string))
  32. {
  33. queryStringParams.Add(ConvertToQueryString(propertyName, value));
  34. }
  35. else
  36. {
  37. queryStringParams.Add($&quot;{propertyName}={Uri.EscapeDataString(value.ToString())}&quot;);
  38. }
  39. }
  40. }
  41. return string.Join(&quot;&amp;&quot;, queryStringParams);
  42. }
  43. }

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:

确定