У c # перетворити анонімний тип у масив ключ / значення?


78

У мене такий анонімний тип:

new {data1 = "test1", data2 = "sam", data3 = "bob"}

Мені потрібен метод, який враховує це і виводить пари значень ключа в масив або словник.

Моя мета - використовувати це як дані публікації в запиті HttpRequest, тому я врешті-решт об'єднаюся в наступний рядок:

"data1=test1&data2=sam&data3=bob"

Відповіді:


119

Для цього потрібно лише крихітний роздум.

var a = new { data1 = "test1", data2 = "sam", data3 = "bob" };
var type = a.GetType();
var props = type.GetProperties();
var pairs = props.Select(x => x.Name + "=" + x.GetValue(a, null)).ToArray();
var result = string.Join("&", pairs);

7
var dict = props.ToDictionary (x => x.Name, x => x.GetValue (a_source, null))
Йорданія

30
Ми дійшли так далеко ... ми можемо зробити це одним лайнером:var dict = a.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(a, null));
nikib3ro

@ kape123, справді. Насправді останні версії .NET більше не вимагають дзвінка ToArray(), що приємно. У будь-якому випадку, відповідь, як вона стоїть, прекрасно вписується в SO без перенесення слів, тому я залишу її такою, як вона є.
Кбрімінгтон,

Відповідь від @ kape123 працює дуже добре, і я не можу знайти з цим проблем (поки що). kbrimington, ти можеш (чи повинен я) додати це до відповіді?
Xan-Kun Clark-Davis

@ Xan-KunClark-Davis, відповідь Капе чудова; проте це насправді та сама відповідь. Ось чому я підтримав його коментар, а не інтегрував його у свою відповідь. Сьогодні, на останніх версіях .NET Frameworks, я взяв би все це і згрупував його як метод розширення. Це покращило б як багаторазове використання, так і ясність.
kbrimington

62

Якщо ви використовуєте .NET 3.5 SP1 або .NET 4, ви можете (ab) використовувати RouteValueDictionaryдля цього. Він реалізує IDictionary<string, object>і має конструктор, який приймає objectта перетворює властивості в пари ключ-значення.

Тоді було б тривіально прокрутити ключі та значення для побудови рядка вашого запиту.


4
Я кажу "зловживання", оскільки клас спочатку був розроблений для маршрутизації (або, принаймні, це означає його назва та простір імен). Однак він не містить специфічних для маршрутизації функцій і вже використовується для інших функцій (наприклад, для перетворення анонімних об'єктів у словники для атрибутів HTML у HtmlHelperметодах розширення ASP.NET MVC .
GWB,

Я зробив саме це, але зараз мені потрібно перейти від RouteValueDictionary назад до аномного об'єкта, будь-які думки ??
Джоші

1
Це навіть не зловживання ним. Це робить Microsoft. Наприклад, коли ви викликаєте HtmlHelper.TextBox ... ви повинні передати анонімний тип для встановлення значень атрибутів. Це насправді призведе до помилки прив'язки в Razor (наприклад, спробуйте зателефонувати до @ Html.Partial ("~ / Shared / _PartialControl.cshtml", new {id = "id", value = "value"}), і це призведе до помилки прив'язки ., навіть з @model динамічним заявив в частковому вигляді метод TextBox внутрішньо викликає «громадський статичний RouteValueDictionary AnonymousObjectToHtmlAttributes (об'єкт htmlAttributes)», який повертає RouteValueDictionary Так що ви йдете ..
Triynko

@Triynko, це просто означає, що Microsoft також зловживає цим. У майбутньому, якщо вони можуть змінити функціонал, щоб зробити щось, що є специфічним для маршрутизації. Я б порекомендував створити власну реалізацію, засновану на джерелі, і дати їй більш підходящу назву.
Нік Коад

6
За винятком того, що залежно від вашої кодової бази, це призведе до залежності від System.Web, яка інакше не потрібна. Бажаю, щоб клас був більш узагальненим і сидів вище / в іншому просторі імен.
Нік Альбрехт,

28

Ось як вони це роблять у RouteValueDictionary:

  private void AddValues(object values)
    {
        if (values != null)
        {
            foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(values))
            {
                object obj2 = descriptor.GetValue(values);
                this.Add(descriptor.Name, obj2);
            }
        }
    }

Повне джерело знаходиться тут: http://pastebin.com/c1gQpBMG


Я намагався використовувати код із pastebin, і Visual Studio говорив, що купа методів словника не реалізована. Мені довелося зробити явний привід для IDictionary. Я щойно переключив пару "this._dictionary" на "((IDictionary <string, object>) this._dictionary)"
Вальтер Стабош,

3

Існує вбудований метод перетворення анонімних об’єктів у словники:

HtmlHelper.AnonymousObjectToHtmlAttributes(yourObj)

Він також повертається RouteValueDictionary. Зверніть увагу, що він статичний


згідно docs.microsoft.com/en-us/previous-versions/aspnet/… "Замінює символи підкреслення (_) дефісами (-) у зазначених атрибутах HTML", у деяких випадках це може бути проблемою.
Лерес Алдтай,

2
using Newtonsoft.Json;
var data = new {data1 = "test1", data2 = "sam", data3 = "bob"};
var encodedData = new FormUrlEncodedContent(JsonConvert.DeserializeObject<Dictionary<string, string>>(JsonConvert.SerializeObject(data))

2
Будь-ласка, додайте пояснення до відповіді, відповіді лише на коди втрачають час рецензента, і його часто неправильно розуміють, навіть можуть видалити.
Мунім Мунна

1

Рішення @ kbrimington робить приємний метод розширення - мій випадок повертає HtmlString

    public static System.Web.HtmlString ToHTMLAttributeString(this Object attributes)
    {
        var props = attributes.GetType().GetProperties();
        var pairs = props.Select(x => string.Format(@"{0}=""{1}""",x.Name,x.GetValue(attributes, null))).ToArray();
        return new HtmlString(string.Join(" ", pairs));
    }

Я використовую його для скидання довільних атрибутів у подання Razor MVC. Я почав з коду, використовуючи RouteValueDictionary та циклічний результат, але це набагато акуратніше.


6
Це вже існує в коробці (принаймні, це зараз):HtmlHelper.AnonymousObjectToHtmlAttributes
Ендрю

1

Я зробив щось подібне:

public class ObjectDictionary : Dictionary<string, object>
{
    /// <summary>
    /// Construct.
    /// </summary>
    /// <param name="a_source">Source object.</param>
    public ObjectDictionary(object a_source)
        : base(ParseObject(a_source))
    {

    }

    /// <summary>
    /// Create a dictionary from the given object (<paramref name="a_source"/>).
    /// </summary>
    /// <param name="a_source">Source object.</param>
    /// <returns>Created dictionary.</returns>
    /// <exception cref="ArgumentNullException">Thrown if <paramref name="a_source"/> is null.</exception>
    private static IDictionary<String, Object> ParseObject(object a_source)
    {
        #region Argument Validation

        if (a_source == null)
            throw new ArgumentNullException("a_source");

        #endregion

        var type = a_source.GetType();
        var props = type.GetProperties();

        return props.ToDictionary(x => x.Name, x => x.GetValue(a_source, null));
    }
}

1

Спираючись на пропозицію @ GWB використовувати a RouteValueDictionary, я написав цю рекурсивну функцію для підтримки вкладених анонімних типів, додавши до цих вкладених параметрів ключі батьків.

public static string EncodeHtmlRequestBody(object data, string parent = null) {
    var keyValuePairs = new List<string>();
    var dict = new RouteValueDictionary(data);

    foreach (var pair in dict) {
        string key = parent == null ? pair.Key : parent + "." + pair.Key;
        var type = pair.Value.GetType();
        if (type.IsPrimitive || type == typeof(decimal) || type == typeof(string)) {
            keyValuePairs.Add(key + "=" + Uri.EscapeDataString((string)pair.Value).Replace("%20", "+"));
        } else {
            keyValuePairs.Add(EncodeHtmlRequestBody(pair.Value, key));
        }
    }

    return String.Join("&", keyValuePairs);
}

Приклад використання:

var data = new {
    apiOperation = "AUTHORIZE",
    order = new {
        id = "order123",
        amount = "101.00",
        currency = "AUD"
    },
    transaction = new {
        id = "transaction123"
    },
    sourceOfFunds = new {
        type = "CARD",
        provided = new {
            card = new {
                expiry = new {
                    month = "1",
                    year = "20"
                },
                nameOnCard = "John Smith",
                number = "4444333322221111",
                securityCode = "123"
            }
        }
    }
};

string encodedData = EncodeHtmlRequestBody(data);

encodedData стає:

"apiOperation=AUTHORIZE&order.id=order123&order.amount=101.00&order.currency=AUD&transaction.id=transaction123&sourceOfFunds.type=CARD&sourceOfFunds.provided.card.expiry.month=1&sourceOfFunds.provided.card.expiry.year=20&sourceOfFunds.provided.card.nameOnCard=John+Smith&sourceOfFunds.provided.card.number=4444333322221111&sourceOfFunds.provided.card.securityCode=123"

Сподіваюся, це допомагає комусь ще в подібній ситуації.

Редагувати: Як зазначив DrewG, це не підтримує масиви. Правильно реалізовувати підтримку довільно вкладених масивів з анонімними типами було б нетривіально, і оскільки жоден з API, які я використовував, також не приймав масиви (я не впевнений, що існує навіть стандартизований спосіб їх серіалізації за допомогою кодування форми), Я залишу це для вас, якщо вам потрібно їх підтримати.


Зверніть увагу, це не обробляє масиви належним чином. Чи буде переповнення стека. потрібно щось подібне if (type.IsArray) {var arr = pair.Value як рядок []; if (arr! = null) {foreach (var s in arr) {keyValuePairs.Add ((key + "[] =" + s). Замінити ("", "+")); }}}
DrewG

0

Занадто пізно, але в будь-якому випадку я додав би це для більш надійного рішення. У тих, кого я бачу тут, є певні проблеми (наприклад, вони не працюють належним чином, скажімо DateTime). З цієї причини я пропоную спочатку перетворити на json (Newtonsoft Json.Net):

var data = new {data1 = "test1", data2 = "sam", data3 = "bob"};

var result = string.Join("&",
            JsonConvert.DeserializeObject<Dictionary<string, string>>(
            JsonConvert.SerializeObject(data))
            .Select(x => $"{x.Key}={x.Value}")
        );
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.