英文:
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 = "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 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<(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)}"));
答案2
得分: 1
以下是翻译好的内容:
如果你寻找的答案不是使用`WebSerializer`方法,
*实现`DataContract`属性似乎是最快的方法,但我强烈怀疑下次如果你只想将`RuleInputModel`实例转换为查询字符串,或者如果你有其他具有`RuleInputModel`类型属性(但名称不同)的模型,结果将导致查询参数名称将有"Data."前缀。*
因此,建议为此编写自定义实现。在这种情况下,预期结果是扁平化的JSON,然后转换为查询字符串。
您可以使用*Newtonsoft.Json*库来实现JSON扁平化,将对象转换为`Dictionary<string, object>`类型,感谢[GuruStron关于使用c#通用地扁平化Json的回答][1]。
第二种方法是将`Dictionary<string, object>`转换为查询字符串。
```csharp
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('&');
}
}
在您的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 "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#][1].
For the second method will be converting the `Dictionary<string, object>` to query string.
```csharp
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();
答案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 = "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
答案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 = "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);
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论