Last active
January 27, 2021 03:53
-
-
Save metastable/41edd51ce238bd669e30fc54c17ef705 to your computer and use it in GitHub Desktop.
PHP QueryStringBuilder in C#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections; | |
| using System.Collections.Generic; | |
| using System.IO; | |
| using System.Linq; | |
| using System.Reflection; | |
| using System.Text; | |
| using JetBrains.Annotations; | |
| using Newtonsoft.Json; | |
| using Newtonsoft.Json.Converters; | |
| namespace AmpleurShipmentSystem.Utils | |
| { | |
| /// <summary> | |
| /// Helps up build a query string by converting an object into a set of named-values and making a | |
| /// query string out of it. | |
| /// </summary> | |
| public class QueryStringBuilder | |
| { | |
| private readonly IList<KeyValuePair<string, object>> _keyValuePairs | |
| = new List<KeyValuePair<string, object>>(); | |
| private readonly JsonSerializer _serializer = new JsonSerializer(); | |
| /// <summary> Builds the query string from the given instance. </summary> | |
| public static string BuildQueryString(object queryData, string argSeperator = "&") | |
| { | |
| var encoder = new QueryStringBuilder(); | |
| encoder.AddEntry(null, queryData, true); | |
| return encoder.GetUriString(argSeperator); | |
| } | |
| /// <summary> | |
| /// Convert the key-value pairs that we've collected into an actual query string. | |
| /// </summary> | |
| private string GetUriString(string argSeperator) | |
| { | |
| return string.Join(argSeperator, | |
| _keyValuePairs.Select(kvp => | |
| { | |
| var key = Uri.EscapeDataString(kvp.Key); | |
| var value = Uri.EscapeDataString(kvp.Value?.ToString() ?? string.Empty); | |
| return $"{key}={value}"; | |
| })); | |
| } | |
| /// <summary> Adds a single entry to the collection. </summary> | |
| /// <param name="prefix"> The prefix to use when generating the key of the entry. Can be null. </param> | |
| /// <param name="instance"> | |
| /// The instance to add. | |
| /// - If the instance is a dictionary, the entries determine the key and values. | |
| /// - If the instance is a collection, the keys will be the index of the entries, and the value | |
| /// will be each item in the collection. | |
| /// - If allowObjects is true, then the object's properties' names will be the keys, and the | |
| /// values of the properties will be the values. | |
| /// - Otherwise the instance is added with the given prefix to the collection of items. | |
| /// </param> | |
| /// <param name="allowObjects"> | |
| /// true to add the properties of the given instance (if the object is | |
| /// not a collection or dictionary), false to add the object as a key-value pair. | |
| /// </param> | |
| private void AddEntry(string prefix, object instance, bool allowObjects) | |
| { | |
| var dictionary = instance as IDictionary; | |
| var collection = instance as ICollection; | |
| if (dictionary != null) | |
| Add(prefix, GetDictionaryAdapter(dictionary)); | |
| else if (collection != null) | |
| Add(prefix, GetArrayAdapter(collection)); | |
| else if (allowObjects) | |
| Add(prefix, GetObjectAdapter(instance)); | |
| else | |
| _keyValuePairs.Add(new KeyValuePair<string, object>(prefix, instance)); | |
| } | |
| /// <summary> Adds the given collection of entries. </summary> | |
| private void Add(string prefix, IEnumerable<Entry> datas) | |
| { | |
| foreach (var item in datas) | |
| { | |
| var newPrefix = string.IsNullOrEmpty(prefix) | |
| ? item.Key | |
| : $"{prefix}[{item.Key}]"; | |
| var instance = item.Value; | |
| var converter = item.Converter; | |
| var allowObjects = !(instance is null || instance is string || instance is Guid || | |
| instance is long || instance is int || instance is short || | |
| instance is ulong || instance is uint || instance is ushort || | |
| instance is double || instance is float || instance is decimal || | |
| converter != null && instance != null && converter.CanConvert(instance.GetType())); | |
| var value = allowObjects ? instance : StringConvert(instance, converter); | |
| AddEntry(newPrefix, value, allowObjects); | |
| } | |
| } | |
| /// <summary> | |
| /// Returns a collection of entries that represent the properties on the object. | |
| /// </summary> | |
| private IEnumerable<Entry> GetObjectAdapter(object data) | |
| { | |
| var properties = data.GetType().GetProperties(); | |
| foreach (var property in properties) | |
| { | |
| var attr = property.GetCustomAttribute(typeof(JsonPropertyAttribute), false) as JsonPropertyAttribute; | |
| var name = attr != null && attr.PropertyName != null ? attr.PropertyName : property.Name; | |
| var value = property.GetValue(data); | |
| JsonConverter converter = null; | |
| var attrconv = | |
| property.GetCustomAttribute(typeof(JsonConverterAttribute), false) as JsonConverterAttribute; | |
| if (attrconv != null) converter = Activator.CreateInstance(attrconv.ConverterType) as JsonConverter; | |
| if (value == null && attr != null && attr.NullValueHandling == NullValueHandling.Ignore) | |
| { | |
| // ignore entry | |
| } | |
| else | |
| { | |
| yield return new Entry | |
| { | |
| Key = name, | |
| Value = value, | |
| Converter = converter | |
| }; | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// Returns a collection of entries that represent items in the collection. | |
| /// </summary> | |
| private IEnumerable<Entry> GetArrayAdapter(ICollection collection) | |
| { | |
| var i = 0; | |
| foreach (var item in collection) | |
| { | |
| yield return new Entry | |
| { | |
| Key = i.ToString(), | |
| Value = item | |
| }; | |
| i++; | |
| } | |
| } | |
| /// <summary> | |
| /// Returns a collection of entries that represent items in the dictionary. | |
| /// </summary> | |
| private IEnumerable<Entry> GetDictionaryAdapter(IDictionary collection) | |
| { | |
| foreach (DictionaryEntry item in collection) | |
| yield return new Entry | |
| { | |
| Key = item.Key.ToString(), | |
| Value = item.Value | |
| }; | |
| } | |
| private string StringConvert([CanBeNull] object value, [CanBeNull] JsonConverter converter) | |
| { | |
| return value switch | |
| { | |
| null => string.Empty, | |
| DateTime time1 when | |
| converter != null && converter.CanConvert(typeof(DateTime)) && | |
| converter is IsoDateTimeConverter conv1 | |
| => time1.ToString(conv1.DateTimeFormat!), | |
| DateTimeOffset time2 when | |
| converter != null && converter.CanConvert(typeof(DateTimeOffset)) && | |
| converter is IsoDateTimeConverter conv2 | |
| => time2.ToString(conv2.DateTimeFormat!), | |
| _ when converter != null && converter.CanConvert(value.GetType()) | |
| => JsonConvert(value, converter), | |
| _ => value.ToString() | |
| }; | |
| } | |
| private string JsonConvert(object value, [CanBeNull] JsonConverter converter = null) | |
| { | |
| var sb = new StringBuilder(); | |
| using var sw = new StringWriter(sb); | |
| using JsonWriter writer = new JsonTextWriter(sw); | |
| writer.Formatting = Formatting.None; | |
| if (converter != null) | |
| converter.WriteJson(writer, value, _serializer); | |
| else | |
| _serializer.Serialize(writer, value); | |
| return sb.ToString(); | |
| } | |
| private struct Entry | |
| { | |
| public string Key; | |
| public object Value; | |
| [CanBeNull] public JsonConverter Converter; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment