Skip to content

Instantly share code, notes, and snippets.

@metastable
Last active January 27, 2021 03:53
Show Gist options
  • Select an option

  • Save metastable/41edd51ce238bd669e30fc54c17ef705 to your computer and use it in GitHub Desktop.

Select an option

Save metastable/41edd51ce238bd669e30fc54c17ef705 to your computer and use it in GitHub Desktop.
PHP QueryStringBuilder in C#
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