Jak mogę dokonać deserializacji JSON do prostego słownika <ciąg, ciąg> w ASP.NET?

682

Mam prostą listę kluczy / wartości w JSON przesyłaną z powrotem do ASP.NET przez POST. Przykład:

{ "key1": "value1", "key2": "value2"}

NIE PRAGNIĘCIE DESERIALIZOWAĆ W MOCNE TYPY OBIEKTÓW .NET

Po prostu potrzebuję zwykłego, starego słownika (ciąg, ciąg) lub jakiegoś równoważnego (tablica skrótu, słownik (ciąg, obiekt), old-school StringDictionary - do diabła, tablica 2-D łańcuchów działałaby dla mnie.

Mogę używać wszystkiego, co jest dostępne w programie ASP.NET 3.5, a także popularnego Json.NET (którego już używam do serializacji do klienta).

Najwyraźniej żadna z tych bibliotek JSON nie ma tej oczywistej możliwości uderzania w czoło od razu po wyjęciu z pudełka - są one całkowicie skupione na deserializacji opartej na odbiciach poprzez silne kontrakty.

Jakieś pomysły?

Ograniczenia:

  1. Nie chcę implementować własnego parsera JSON
  2. Nie można jeszcze używać programu ASP.NET 4.0
  3. Wolałby trzymać się z dala od starszej, przestarzałej klasy ASP.NET dla JSON
richardtallent
źródło
1
re: ograniczenie 3, JavaScriptSerizlizerjest używany w ASP.NET MVC i nie jest już przestarzały.
bdukes
17
to niewiarygodne, jak trudno było znaleźć prosty sposób na konwersję łańcucha JSON na coś, z czego mógłbym łatwo korzystać bez przechodzenia przez wiele różnych przepływów stosu. W innych językach jest to takie proste, ale Java i C # wydają się robić wszystko, co utrudnia życie.
user299709

Odpowiedzi:

893

Json.NET robi to ...

string json = @"{""key1"":""value1"",""key2"":""value2""}";

var values = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);

Więcej przykładów: serializowanie kolekcji za pomocą Json.NET

James Newton-King
źródło
9
Czy to działa również, gdy wartości są liczbami całkowitymi. Czy są automatycznie rzutowane na „ciągi”?
Highmastdon
58
@Highmastdon Nie, nie ma. Znalazłem najlepszy sposób na deserializację do słownika to użycie dynamicjako typu wartości:JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(json);
Erik Schierboom
1
Wypróbowałem kilka odpowiedzi na tej stronie z bardzo niechlujną parą klucz / wartość, a JSON.NET był jedyną, którą próbowałem.
bnieland
15
Nie działa, jeśli używasz tablicy par klucz-wartość w json [{key: "a", value: "1"}, {key: "b", value:"2"}], musisz zrobić coś takiego:var dict = JsonConvert.DeserializeObject<List<KeyValuePair<string, string>>>(json);
Adrian
8
Nie działa również, jeśli wartości są obiektami zagnieżdżonymi, ponieważ json.net tworzy je jako obiekty JObjects
Kugel,
100

Odkryłem, że .NET ma wbudowany sposób, aby rzutować ciąg JSON na typ Dictionary<String, Object>via System.Web.Script.Serialization.JavaScriptSerializerw zestawie 3.5 System.Web.Extensions. Użyj metody DeserializeObject(String).

Natknąłem się na to, gdy robiłem posta ajax (poprzez jquery) typu zawartości „application / json” do statycznej metody strony .net i zobaczyłem, że metoda (która miała jeden parametr typu Object) magicznie otrzymała ten słownik.

Chrupiący
źródło
5
ale wbudowany javascriptserializer działa lepiej niż json.net, to rozwiązanie jest lepsze. Na przykład javascriptseralizer zwróci wartości null zamiast pustych ciągów i w ogóle nie działa w przypadku właściwości zerowalnych itd.
pilavdzice
1
@pilavdzice Nie wspominając już o przyjemności, jaką masz podczas analizowania dat, ponieważ zakłada to niestandardowy format daty MS.
Podstawowy
16
Przykład szybkiego kodu: var jsSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();po którym następujeDictionary<string, object> dict = (Dictionary<string, object>)jsSerializer.DeserializeObject(jsonString);
Nate Cook
6
Zaletą przykładu Nate Cook w prostym przypadku jest uniknięcie potrzeby stosowania zewnętrznych bibliotek DLL. Uzyskuję dostęp do interfejsu API z autonomicznej konsoli, która może polegać tylko na środowisku .Net.
Nick.T
@pilavdzice Czy możesz się bardziej szczegółowo o tym dowiedzieć? Nie mogę odtworzyć rzeczy „return null zamiast blank strings”, dało mi to wartość pustego ciągu dlaSomeData: ""
jrh
51

Dla tych, którzy szukają w Internecie i natkną się na ten post, napisałem post na blogu na temat korzystania z klasy JavaScriptSerializer.

Czytaj więcej ... http://procbits.com/2011/04/21/quick-json-serializationdeserialization-in-c/

Oto przykład:

var json = "{\"id\":\"13\", \"value\": true}";
var jss = new JavaScriptSerializer();
var table = jss.Deserialize<dynamic>(json);
Console.WriteLine(table["id"]);
Console.WriteLine(table["value"]);
JP Richardson
źródło
hm, wypróbowałem twoje rozwiązanie ... Mam JSona {{id ":" 13 "," value ": true} i dla mnie działa tylko rozwiązanie Dictionary <dynamic>
Marko
ok, znalazłem, gdzie jest problem ... musisz dodać [] po deklaracji słownika, aby poprawnie przekształcić z postaci szeregowej ... Dodaję też komentarz do twojego wpisu na blogu ... na zdrowie;)
Marko
Zaktualizowałem swoją odpowiedź, aby odzwierciedlić Twój konkretny zestaw danych. Działa dobrze z dynamiką.
JP Richardson
Właśnie napisałem inny parser JSON, który jest nieco bardziej elastyczny i obsługuje Silverlight: procbits.com/2011/08/08/11/…
JP Richardson
41

Próbowałem nie używać żadnej zewnętrznej implementacji JSON, więc dokonałem deserializacji w następujący sposób:

string json = "{\"id\":\"13\", \"value\": true}";

var serializer = new JavaScriptSerializer(); //using System.Web.Script.Serialization;

Dictionary<string, string> values = serializer.Deserialize<Dictionary<string, string>>(json);
PvH
źródło
6
Dodaj referencję System.Web.Extensions, aby korzystać z System.Web.Script
Patrick Cullen
1
Najbardziej podoba mi się ta odpowiedź, ponieważ jest prosta i korzysta z platformy .NET System.Web.Script.Serialization. To po prostu działa. Byłem nawet w stanie użyć „niepoprawnego” JSON jak string json = "{'id':13, 'value': true}";.
styfle
Z ciekawości, czy istnieje ten sam jeden liniowy sposób na deserializację w słowniku OrdinalIgnoreCase?
batbaatar,
38

Miałem ten sam problem, więc napisałem to sam. To rozwiązanie różni się od innych odpowiedzi, ponieważ może dokonać deserializacji na wiele poziomów.

Wystarczy wysłać ciąg JSON, aby deserializeToDictionary funkcja zwróciła obiekt nietypowy Dictionary<string, object>.

Stary kod

private Dictionary<string, object> deserializeToDictionary(string jo)
{
    var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(jo);
    var values2 = new Dictionary<string, object>();
    foreach (KeyValuePair<string, object> d in values)
    {
        // if (d.Value.GetType().FullName.Contains("Newtonsoft.Json.Linq.JObject"))
        if (d.Value is JObject)
        {
            values2.Add(d.Key, deserializeToDictionary(d.Value.ToString()));
        }
        else
        {
            values2.Add(d.Key, d.Value);
        }
    }
    return values2;
}

Przykład: Zwróci Dictionary<string, object>obiekt odpowiedzi JSON na Facebooku.

Test

private void button1_Click(object sender, EventArgs e)
{
    string responsestring = "{\"id\":\"721055828\",\"name\":\"Dasun Sameera Weerasinghe\",\"first_name\":\"Dasun\",\"middle_name\":\"Sameera\",\"last_name\":\"Weerasinghe\",\"username\":\"dasun\",\"gender\":\"male\",\"locale\":\"en_US\",  hometown: {id: \"108388329191258\", name: \"Moratuwa, Sri Lanka\",}}";
    Dictionary<string, object> values = deserializeToDictionary(responsestring);
}

Uwaga: miasto rodzinne dalej dezaktywuje się w Dictionary<string, object> obiekt.

Aktualizacja

Moja stara odpowiedź działa świetnie, jeśli w łańcuchu JSON nie ma tablicy. Ten dodatkowo deserializuje się w List<object>elemencie if, jeśli jest tablicą.

Wystarczy wysłać ciąg JSON, aby deserializeToDictionaryOrList funkcja zwróci obiekt nietypowy Dictionary<string, object>lub List<object>.

private static object deserializeToDictionaryOrList(string jo,bool isArray=false)
{
    if (!isArray)
    {
        isArray = jo.Substring(0, 1) == "[";
    }
    if (!isArray)
    {
        var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(jo);
        var values2 = new Dictionary<string, object>();
        foreach (KeyValuePair<string, object> d in values)
        {
            if (d.Value is JObject)
            {
                values2.Add(d.Key, deserializeToDictionary(d.Value.ToString()));
            }
            else if (d.Value is JArray)
            {
                values2.Add(d.Key, deserializeToDictionary(d.Value.ToString(), true));
            }
            else
            {
                values2.Add(d.Key, d.Value);
            }
        }
        return values2;
    }else
    {
        var values = JsonConvert.DeserializeObject<List<object>>(jo);
        var values2 = new List<object>();
        foreach (var d in values)
        {
            if (d is JObject)
            {
                values2.Add(deserializeToDictionary(d.ToString()));
            }
            else if (d is JArray)
            {
                values2.Add(deserializeToDictionary(d.ToString(), true));
            }
            else
            {
                values2.Add(d);
            }
        }
        return values2;
    }
}
Dasun
źródło
@Jordan, dziękuję za zwrócenie uwagi, dokonałem pewnych modyfikacji tego kodu, ale nie mam go teraz. Ten kod nie obsługuje obiektów JArray, zaktualizuję kod, gdy go już otrzymam.
Dasun,
1
Żaden problem. Wspominam o tym tylko dlatego, że poznanie operatorów isi asoperatorów bardzo mi pomogło i uprościło mój własny kod.
Jordan
Działa, ale nie jest wydajna, ponieważ wywołuje ToString, a następnie ponownie deserializuje. Spójrz na odpowiedź Falko poniżej. Jest deserializować ciąg źródłowy tylko raz.
Siergiej Zinowjew
1
Odpowiedź Falko działa tylko wtedy, gdy znasz strukturę danych z góry. To rozwiązanie może być użyte dla dowolnego ciągu JSON.
Dasun
16

Jeśli zależy Ci na lekkim podejściu bez dodawania odniesień, być może ten fragment kodu, który właśnie napisałem, zadziała (chociaż nie mogę w 100% zagwarantować solidności).

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

public Dictionary<string, object> ParseJSON(string json)
{
    int end;
    return ParseJSON(json, 0, out end);
}
private Dictionary<string, object> ParseJSON(string json, int start, out int end)
{
    Dictionary<string, object> dict = new Dictionary<string, object>();
    bool escbegin = false;
    bool escend = false;
    bool inquotes = false;
    string key = null;
    int cend;
    StringBuilder sb = new StringBuilder();
    Dictionary<string, object> child = null;
    List<object> arraylist = null;
    Regex regex = new Regex(@"\\u([0-9a-z]{4})", RegexOptions.IgnoreCase);
    int autoKey = 0;
    for (int i = start; i < json.Length; i++)
    {
        char c = json[i];
        if (c == '\\') escbegin = !escbegin;
        if (!escbegin)
        {
            if (c == '"')
            {
                inquotes = !inquotes;
                if (!inquotes && arraylist != null)
                {
                    arraylist.Add(DecodeString(regex, sb.ToString()));
                    sb.Length = 0;
                }
                continue;
            }
            if (!inquotes)
            {
                switch (c)
                {
                    case '{':
                        if (i != start)
                        {
                            child = ParseJSON(json, i, out cend);
                            if (arraylist != null) arraylist.Add(child);
                            else
                            {
                                dict.Add(key, child);
                                key = null;
                            }
                            i = cend;
                        }
                        continue;
                    case '}':
                        end = i;
                        if (key != null)
                        {
                            if (arraylist != null) dict.Add(key, arraylist);
                            else dict.Add(key, DecodeString(regex, sb.ToString()));
                        }
                        return dict;
                    case '[':
                        arraylist = new List<object>();
                        continue;
                    case ']':
                        if (key == null)
                        {
                            key = "array" + autoKey.ToString();
                            autoKey++;
                        }
                        if (arraylist != null && sb.Length > 0)
                        {
                            arraylist.Add(sb.ToString());
                            sb.Length = 0;
                        }
                        dict.Add(key, arraylist);
                        arraylist = null;
                        key = null;
                        continue;
                    case ',':
                        if (arraylist == null && key != null)
                        {
                            dict.Add(key, DecodeString(regex, sb.ToString()));
                            key = null;
                            sb.Length = 0;
                        }
                        if (arraylist != null && sb.Length > 0)
                        {
                            arraylist.Add(sb.ToString());
                            sb.Length = 0;
                        }
                       continue;
                    case ':':
                        key = DecodeString(regex, sb.ToString());
                        sb.Length = 0;
                        continue;
                }
            }
        }
        sb.Append(c);
        if (escend) escbegin = false;
        if (escbegin) escend = true;
        else escend = false;
    }
    end = json.Length - 1;
    return dict; //theoretically shouldn't ever get here
}
private string DecodeString(Regex regex, string str)
{
    return Regex.Unescape(regex.Replace(str, match => char.ConvertFromUtf32(Int32.Parse(match.Groups[1].Value, System.Globalization.NumberStyles.HexNumber))));
}

[Zdaję sobie sprawę, że to narusza Ograniczenie OP nr 1, ale technicznie tego nie napisałeś, ja zrobiłem]

zręczność
źródło
3
To jedyna odpowiedź działająca w Silverlight i bez zależności! Silverlight nie ma JavascriptSerializer ani Serializable. Brak zależności oznacza brak Json.NET, RestSharp lub MiniJSON. Tylko @DanCsharpster wypróbował inne możliwe rozwiązanie, ale niestety nie działało to dla mnie tak jak to.
Cœur
1
Co jest złego w dodawaniu odwołania do czegoś prostego, takiego jak JSON.NET? Czy to musi być tak lekkie, że nie można niczego odwoływać? Nie twierdzę, że Twój kod nie będzie działał, ale za każdym razem, gdy rzucisz własny, oczywiście ryzykujesz, że Twój kod nie będzie tak niezawodny, na przykład w przypadkach brzegowych, ani szybko jak przetestowana biblioteka, taka jak JSON.NET.
Dan Csharpster
1
Rzut własny to zły pomysł, gdy masz dobrą alternatywę. Nie znam żadnej sytuacji, która musiałaby być tak lekka. Wolałbym mieć mniej optymalny kod, który jest łatwy do odczytania i zmiany.
Jordan
3
Pierwotnie napisałem ten fragment kodu, ponieważ nie miałem alternatywy. Zastanów się nad takimi rzeczami, jak Silverlight lub dostawcy różnych typów produktów Office, gdzie dodanie zewnętrznych odniesień do projektu jest albo wyjątkowo problematyczne, albo niemożliwe.
zręczność
Wiem, że to kilka lat później, ale to wciąż bardzo ważne pytanie. Dla każdego, kto zastanawia się, dlaczego chcielibyśmy przejść na tak lekki, cóż, jeśli pracujesz z SQL CLR C #, jest tylko tyle „bezpiecznych” bibliotek, z których możesz korzystać i System.RunTime.Serializationnie jest jedną z nich, niestety JSON.NET zależy od i dlatego też nie można go użyć. Dzięki dexy za twoją świetną pracę, odważyłem się trochę ulepszyć, aby móc deserializować tablice tablic, zobacz zaktualizowany kod w mojej odpowiedzi tutaj .
Alberto Rechy
15

Musiałem tylko przeanalizować zagnieżdżony słownik, na przykład

{
    "x": {
        "a": 1,
        "b": 2,
        "c": 3
    }
}

gdzie JsonConvert.DeserializeObjectnie pomaga. Znalazłem następujące podejście:

var dict = JObject.Parse(json).SelectToken("x").ToObject<Dictionary<string, int>>();

SelectTokenPozwala kopać dół do żądanego pola. Możesz nawet określić ścieżkę, np. "x.y.z"Zejść dalej do obiektu JSON.

Falko
źródło
JObject.Parse (json) .ToObject <Słownik <Guid, Lista <int> >> () działał dla mnie w moim scenariuszu dzięki
geedubb
11

System.Text.Json

Można to teraz zrobić za pomocą System.Text.Jsonwbudowanego w .net core 3.0. Można teraz dokonać deserializacji JSON bez korzystania z bibliotek stron trzecich.

var json = @"{""key1"":""value1"",""key2"":""value2""}";
var values = JsonSerializer.Deserialize<Dictionary<string, string>>(json);

Dostępne również w pakiecie nu-get System.Text.Json, jeśli używasz .Net Standard lub .Net Framework.

haldo
źródło
1
Tak! System.Text.Jsonjest sposobem, aby przejść w tych dniach.
mfluehr
2
Tak, wygląda obiecująco! Należy jednak pamiętać, że domyślna wersja System.Text.Json .NET Core 3.1 nie obsługuje deserializacji słowników za pomocą kluczy nieciągłych. Podczas gdy mój OP dotyczył ciągów, w praktyce mam wiele kluczy Guid, więc to mnie „ugryzło” podczas próby zmiany. Nie ma również odpowiedników niektórych atrybutów (wymagane itp.).
richardtallent
6

Mark Rendle opublikował to jako komentarz , chciałem opublikować to jako odpowiedź, ponieważ jest to jedyne rozwiązanie, które do tej pory działało, aby zwrócić sukces i kody błędów JSON z odpowiedzi Google reCaptcha.

string jsonReponseString= wClient.DownloadString(requestUrl);    
IDictionary<string, object> dict = new JavaScriptSerializer().DeserializeObject(jsonReponseString) as IDictionary<string, object>;

Jeszcze raz dziękuję, Mark!

Bryan
źródło
1
JavaScriptSerializer został prawie przestarzały. Dokumentacja mówi, że powinniśmy używać JSON.NET ( docs.microsoft.com/en-us/dotnet/api/… )
Mario Lopez
Również dobre dla starszych aplikacji internetowych, w których nie chcesz uwzględniać dodatkowych zależności.
Andrew Grothe,
5

Edycja: Działa, ale zaakceptowana odpowiedź przy użyciu Json.NET jest znacznie prostsza. Pozostawienie tego na wypadek, gdyby ktoś potrzebował tylko kodu BCL.

Nie jest obsługiwany przez system .NET po wyjęciu z pudełka. Rażący niedopatrzenie - nie wszyscy muszą deserializować na obiekty o nazwanych właściwościach. Skończyło się na tym, że sami:

<Serializable()> Public Class StringStringDictionary
    Implements ISerializable
    Public dict As System.Collections.Generic.Dictionary(Of String, String)
    Public Sub New()
        dict = New System.Collections.Generic.Dictionary(Of String, String)
    End Sub
    Protected Sub New(info As SerializationInfo, _
          context As StreamingContext)
        dict = New System.Collections.Generic.Dictionary(Of String, String)
        For Each entry As SerializationEntry In info
            dict.Add(entry.Name, DirectCast(entry.Value, String))
        Next
    End Sub
    Public Sub GetObjectData(info As SerializationInfo, context As StreamingContext) Implements ISerializable.GetObjectData
        For Each key As String in dict.Keys
            info.AddValue(key, dict.Item(key))
        Next
    End Sub
End Class

Dzwoni z:

string MyJsonString = "{ \"key1\": \"value1\", \"key2\": \"value2\"}";
System.Runtime.Serialization.Json.DataContractJsonSerializer dcjs = new
  System.Runtime.Serialization.Json.DataContractJsonSerializer(
    typeof(StringStringDictionary));
System.IO.MemoryStream ms = new
  System.IO.MemoryStream(Encoding.UTF8.GetBytes(MyJsonString));
StringStringDictionary myfields = (StringStringDictionary)dcjs.ReadObject(ms);
Response.Write("Value of key2: " + myfields.dict["key2"]);

Przepraszamy za połączenie C # i VB.NET…

richardtallent
źródło
2
[TestMethod] public void TestSimpleObject () {const string json = @ "{" "Name" ":" "Bob" "," "Age" ": 42}"; var dict = new JavaScriptSerializer (). DeserializeObject (json) jako IDictionary <ciąg, obiekt>; Assert.IsNotNull (dict); Assert.IsTrue (dict.ContainsKey („Nazwa”)); Assert.AreEqual („Bob”, dict [„Name”]); Assert.IsTrue (dict.ContainsKey („Age”)); Assert.AreEqual (42, dict [„Age”]); }
Mark Rendle
1
To jest fantastyczne. Pomaga w implementacjach usług WCF, które łączą się za pomocą JSON z klientami opartymi na przeglądarce.
Anton
@ Mark Rendle: Twoja implementacja jest bardzo prosta i jest TYLKO tą, która do tej pory działała dla mnie zarówno w uzyskiwaniu rezultatów, jak i kodów błędów JSON. Wypróbowałem wiele rozwiązań, więc dziękuję za opublikowanie tego w komentarzu. To powinna być odpowiedź.
Bryan
5

Dodałem tutaj kod przesłany przez jSnake04 i Dasun. Dodałem kod do tworzenia list obiektów z JArrayinstancji. Ma dwukierunkową rekurencję, ale ponieważ działa na stałym, skończonym modelu drzewa, nie ma ryzyka przepełnienia stosu, chyba że dane są ogromne.

/// <summary>
/// Deserialize the given JSON string data (<paramref name="data"/>) into a
///   dictionary.
/// </summary>
/// <param name="data">JSON string.</param>
/// <returns>Deserialized dictionary.</returns>
private IDictionary<string, object> DeserializeData(string data)
{
    var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(data);

    return DeserializeData(values);
}

/// <summary>
/// Deserialize the given JSON object (<paramref name="data"/>) into a dictionary.
/// </summary>
/// <param name="data">JSON object.</param>
/// <returns>Deserialized dictionary.</returns>
private IDictionary<string, object> DeserializeData(JObject data)
{
    var dict = data.ToObject<Dictionary<String, Object>>();

    return DeserializeData(dict);
}

/// <summary>
/// Deserialize any elements of the given data dictionary (<paramref name="data"/>) 
///   that are JSON object or JSON arrays into dictionaries or lists respectively.
/// </summary>
/// <param name="data">Data dictionary.</param>
/// <returns>Deserialized dictionary.</returns>
private IDictionary<string, object> DeserializeData(IDictionary<string, object> data)
{
    foreach (var key in data.Keys.ToArray()) 
    {
        var value = data[key];

        if (value is JObject)
            data[key] = DeserializeData(value as JObject);

        if (value is JArray)
            data[key] = DeserializeData(value as JArray);
    }

    return data;
}

/// <summary>
/// Deserialize the given JSON array (<paramref name="data"/>) into a list.
/// </summary>
/// <param name="data">Data dictionary.</param>
/// <returns>Deserialized list.</returns>
private IList<Object> DeserializeData(JArray data)
{
    var list = data.ToObject<List<Object>>();

    for (int i = 0; i < list.Count; i++)
    {
        var value = list[i];

        if (value is JObject)
            list[i] = DeserializeData(value as JObject);

        if (value is JArray)
            list[i] = DeserializeData(value as JArray);
    }

    return list;
}
Jordania
źródło
4

Do drugiej odpowiedzi dodałem sprawdzanie wartości pustych w JSON

Miałem ten sam problem, więc napisałem to sobie. To rozwiązanie różni się od innych odpowiedzi, ponieważ może dokonać deserializacji na wiele poziomów.

Wystarczy wysłać ciąg json, aby deserializeToDictionary funkcja zwróciła obiekt nietypowy Dictionary<string, object>.

private Dictionary<string, object> deserializeToDictionary(string jo)
{
    var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(jo);
    var values2 = new Dictionary<string, object>();
    foreach (KeyValuePair<string, object> d in values)
    {
        if (d.Value != null && d.Value.GetType().FullName.Contains("Newtonsoft.Json.Linq.JObject"))
        {
            values2.Add(d.Key, deserializeToDictionary(d.Value.ToString()));
        }
        else
        {
            values2.Add(d.Key, d.Value);
        }
    }
    return values2;
}

Przykład: Zwróci Dictionary<string, object>obiekt odpowiedzi JSON na Facebooku.

private void button1_Click(object sender, EventArgs e)
{
    string responsestring = "{\"id\":\"721055828\",\"name\":\"Dasun Sameera
        Weerasinghe\",\"first_name\":\"Dasun\",\"middle_name\":\"Sameera\",\"last_name\":\"Weerasinghe\",\"username\":\"dasun\",\"gender\":\"male\",\"locale\":\"en_US\",
        hometown: {id: \"108388329191258\", name: \"Moratuwa, Sri Lanka\",}}";
    Dictionary<string, object> values = deserializeToDictionary(responsestring);
}

Uwaga: miasto rodzinne dalej przekształca Dictionary<string, object>obiekt w postaci szeregowej.

jSnake04
źródło
1
+1 Jak powiedziałem z Dasun powyżej. Możesz po prostu sprawdzić, czy d.Value is JObject. Nie musisz przechodzić refleksji, aby sprawdzić typy. Z isoperatorem nie musisz sprawdzać, czy nie ma wartości null. Zwraca false, jeśli obiekt ma wartość NULL.
Jordan
3

Wygląda na to, że wszystkie te odpowiedzi tutaj zakładają, że możesz wyciągnąć ten niewielki ciąg z większego obiektu ... dla osób, które chcą po prostu zdezrealizować duży obiekt za pomocą takiego słownika gdzieś w odwzorowaniu i które używają System.Runtime.Serialization.Json systemu DataContract, oto rozwiązanie:

Odpowiedź na gis.stackexchange.com miała ten interesujący link . Musiałem go odzyskać za pomocą archive.org, ale oferuje całkiem idealne rozwiązanie: niestandardoweIDataContractSurrogate klasę, w której implementujesz dokładnie własne typy. Z łatwością mogłem go rozwinąć.

Dokonałem jednak wielu zmian. Ponieważ oryginalne źródło nie jest już dostępne, opublikuję tutaj całą klasę:

using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;

namespace JsonTools
{
    /// <summary>
    /// Allows using Dictionary&lt;String,String&gt; and Dictionary&lt;String,Boolean&gt; types, and any others you'd like to add.
    /// Source: https://web.archive.org/web/20100317222656/my6solutions.com/post/2009/06/30/DataContractSerializer-DataContractJsonSerializer-JavaScriptSerializer-XmlSerializer-for-serialization.aspx
    /// </summary>
    public class JsonSurrogate : IDataContractSurrogate
    {
        /// <summary>
        /// Deserialize an object with added support for the types defined in this class.
        /// </summary>
        /// <typeparam name="T">Contract class</typeparam>
        /// <param name="json">JSON String</param>
        /// <param name="encoding">Text encoding</param>
        /// <returns>The deserialized object of type T</returns>
        public static T Deserialize<T>(String json, Encoding encoding)
        {
            if (encoding == null)
                encoding = new UTF8Encoding(false);
            DataContractJsonSerializer deserializer = new DataContractJsonSerializer(
                typeof(T), new Type[0], int.MaxValue, true, new JsonSurrogate(), false);
            using (MemoryStream stream = new MemoryStream(encoding.GetBytes(json)))
            {
                T result = (T)deserializer.ReadObject(stream);
                return result;
            }
        }

        // make sure all values in this are classes implementing JsonSurrogateObject.
        private static Dictionary<Type, Type> KnownTypes = 
            new Dictionary<Type, Type>()
            {
                {typeof(Dictionary<String, String>), typeof(SSDictionary)},
                {typeof(Dictionary<String, Boolean>), typeof(SBDictionary)}
            };

        #region Implemented surrogate dictionary classes

        [Serializable]
        public class SSDictionary : SurrogateDictionary<String>
        {
            public SSDictionary() : base() {}
            protected SSDictionary (SerializationInfo info, StreamingContext context) : base(info, context) {}
        }
        [Serializable]
        public class SBDictionary : SurrogateDictionary<Boolean>
        {
            public SBDictionary() : base() {}
            protected SBDictionary (SerializationInfo info, StreamingContext context) : base(info, context) {}
        }

        #endregion

        /// <summary>Small interface to easily extract the final value from the object.</summary>
        public interface JsonSurrogateObject
        {
            Object DeserializedObject { get; }
        }

        /// <summary>
        /// Class for deserializing any simple dictionary types with a string as key.
        /// </summary>
        /// <typeparam name="T">Any simple type that will be deserialized correctly.</typeparam>
            [Serializable]
        public abstract class SurrogateDictionary<T> : ISerializable, JsonSurrogateObject
        {
            public Object DeserializedObject { get { return dict; } }
            private Dictionary<String, T> dict;

            public SurrogateDictionary()
            {
                dict = new Dictionary<String, T>();
            }

            // deserialize
            protected SurrogateDictionary(SerializationInfo info, StreamingContext context)
            {
                dict = new Dictionary<String, T>();
                foreach (SerializationEntry entry in info)
                {
                    // This cast will only work for base types, of course.
                    dict.Add(entry.Name, (T)entry.Value);
                }
            }
            // serialize
            public void GetObjectData(SerializationInfo info, StreamingContext context)
            {
                foreach (String key in dict.Keys)
                {
                    info.AddValue(key, dict[key]);
                }
            }

        }

        /// <summary>
            /// Uses the KnownTypes dictionary to get the surrogate classes.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public Type GetDataContractType(Type type)
        {
            Type returnType;
            if (KnownTypes.TryGetValue(type, out returnType))
            {
                return returnType;
            }
            return type;
        }

        public object GetObjectToSerialize(object obj, Type targetType)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        ///     Gets the object out of the surrogate datacontract object. This function is the reason all surrogate objects need to implement the JsonSurrogateObject class.
        /// </summary>
        /// <param name="obj">Result of the deserialization</param>
        /// <param name="targetType">Expected target type of the deserialization</param>
        /// <returns></returns>
        public object GetDeserializedObject(object obj, Type targetType)
        {
            if (obj is JsonSurrogateObject)
            {
                return ((JsonSurrogateObject)obj).DeserializedObject;
            }
            return obj;
        }

        public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
        {
            return null;
        }

        #region not implemented

        public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
        {
            throw new NotImplementedException();
        }

        public object GetCustomDataToExport(Type clrType, Type dataContractType)
        {
            throw new NotImplementedException();
        }

        public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
        {
            throw new NotImplementedException();
        }

        public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

Aby dodać nowe obsługiwane typy do klasy, wystarczy dodać klasę, nadać jej odpowiednie konstruktory i funkcje (spójrz na SurrogateDictionaryprzykład), upewnić się, że dziedziczy JsonSurrogateObjecti dodać mapowanie typów do KnownTypessłownika. Dołączony SurrogateDictionary może służyć jako podstawa dla każdegoDictionary<String,T> typów, w których T jest dowolnym typem, który dokonuje prawidłowej deserializacji.

Wywołanie tego jest naprawdę proste:

MyObjtype newObj = JsonSurrogate.Deserialize<MyObjtype>(jsonStr, encoding);

Zauważ, że z jakiegoś powodu ta rzecz ma problem z używaniem ciągów znaków zawierających spacje; po prostu nie było ich na końcowej liście. Może po prostu wbrew specyfikacjom JSON, a api, do której dzwoniłem, był źle wdrożony, pamiętajcie; Nie wiem. W każdym razie rozwiązałem ten problem, zastępując je wyrażeniami regularnymi podkreśleniami w surowych danych JSON i naprawiając słownik po deserializacji.

Nyerguds
źródło
Nawiasem mówiąc, z jakiegoś szczególnego powodu Mono wydaje się mieć problemy z uruchomieniem tych rzeczy ...
Nyerguds
Dzięki za udostępnienie, niestety to rozwiązanie nie obsługuje nieprymitywnych typów i nie ma sposobu, aby uzyskać surową wartość, więc możesz sam ją zbudować. Jeśli zarejestruję mój niestandardowy typ w znanym typie i użyję go w słowniku, najpierw wywoła słownik, oczekiwałbym, że zacznie analizować od dołu do góry od najbardziej odległych typów do bardziej złożonych.
Ivan Leonenko
Pytanie tylko o to Dictionary<String,String>. Szczerze mówiąc nigdy nie próbowałem deserializować złożonych typów za pomocą tego systemu.
Nyerguds
3

Na podstawie powyższych komentarzy spróbujJsonConvert.DeserializeObject<Dictionary<string,dynamic>>(json)

var json = @"{""key1"":1,""key2"":""value2"", ""object1"":{""property1"":""value1"",""property2"":[2,3,4,5,6,7]}}";
var parsedObject = JsonConvert.DeserializeObject<Dictionary<string,dynamic>>(json);

wydaje się działać nawet w przypadku złożonych obiektów i list.

Pięść furii
źródło
1

Właśnie zaimplementowałem to w RestSharp . Ten post był dla mnie pomocny.

Oprócz kodu w linku, oto mój kod. Teraz uzyskuję Dictionarywyniki, gdy robię coś takiego:

var jsonClient = new RestClient(url.Host);
jsonClient.AddHandler("application/json", new DynamicJsonDeserializer());
var jsonRequest = new RestRequest(url.Query, Method.GET);
Dictionary<string, dynamic> response = jsonClient.Execute<JObject>(jsonRequest).Data.ToObject<Dictionary<string, dynamic>>();

Uważaj na rodzaj JSON, którego oczekujesz - w moim przypadku pobierałem pojedynczy obiekt o kilku właściwościach. W załączonym linku autor pobierał listę.

Northben
źródło
1

Moje podejście bezpośrednio deserializuje się do IDictionary, bez JObject lub ExpandObject pomiędzy. Kod wykorzystuje konwerter, który jest w zasadzie kopiowany z klasy ExpandoObjectConverter znalezionej w kodzie źródłowym JSON.NET, ale używa IDictionary zamiast ExpandoObject.

Stosowanie:

var settings = new JsonSerializerSettings()
{
    Converters = { new DictionaryConverter() },
};
var result = JsonConvert.DeserializeObject<IDictionary<string, object>>(json, settings);

Kod:

// based on ExpandoObjectConverter, but using arrays instead of IList, to behave similar to System.Web.Script.Serialization.JavaScriptSerializer
public class DictionaryConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return ReadValue(reader);
    }

    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(IDictionary<string, object>));
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    private object ReadValue(JsonReader reader)
    {
        while (reader.TokenType == JsonToken.Comment)
        {
            if (!reader.Read())
                throw JsonSerializationExceptionCreate(reader, "Unexpected end when reading IDictionary<string, object>.");
        }

        switch (reader.TokenType)
        {
            case JsonToken.StartObject:
                return ReadObject(reader);
            case JsonToken.StartArray:
                return ReadList(reader);
            default:
                if (IsPrimitiveToken(reader.TokenType))
                    return reader.Value;

                throw JsonSerializationExceptionCreate(reader, string.Format(CultureInfo.InvariantCulture, "Unexpected token when converting IDictionary<string, object>: {0}", reader.TokenType));
        }
    }

    private object ReadList(JsonReader reader)
    {
        List<object> list = new List<object>();

        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.Comment:
                    break;
                default:
                    object v = ReadValue(reader);

                    list.Add(v);
                    break;
                case JsonToken.EndArray:
                    return list;
            }
        }

        throw JsonSerializationExceptionCreate(reader, "Unexpected end when reading IDictionary<string, object>.");
    }

    private object ReadObject(JsonReader reader)
    {
        IDictionary<string, object> dictionary = new Dictionary<string, object>();
        while (reader.Read())
        {
            switch (reader.TokenType)
            {
                case JsonToken.PropertyName:
                    string propertyName = reader.Value.ToString();

                    if (!reader.Read())
                        throw JsonSerializationExceptionCreate(reader, "Unexpected end when reading IDictionary<string, object>.");

                    object v = ReadValue(reader);

                    dictionary[propertyName] = v;
                    break;
                case JsonToken.Comment:
                    break;
                case JsonToken.EndObject:
                    return dictionary;
            }
        }

        throw JsonSerializationExceptionCreate(reader, "Unexpected end when reading IDictionary<string, object>.");
    }

    //based on internal Newtonsoft.Json.JsonReader.IsPrimitiveToken
    internal static bool IsPrimitiveToken(JsonToken token)
    {
        switch (token)
        {
            case JsonToken.Integer:
            case JsonToken.Float:
            case JsonToken.String:
            case JsonToken.Boolean:
            case JsonToken.Undefined:
            case JsonToken.Null:
            case JsonToken.Date:
            case JsonToken.Bytes:
                return true;
            default:
                return false;
        }
    }

    // based on internal Newtonsoft.Json.JsonSerializationException.Create
    private static JsonSerializationException JsonSerializationExceptionCreate(JsonReader reader, string message, Exception ex = null)
    {
        return JsonSerializationExceptionCreate(reader as IJsonLineInfo, reader.Path, message, ex);
    }

    // based on internal Newtonsoft.Json.JsonSerializationException.Create
    private static JsonSerializationException JsonSerializationExceptionCreate(IJsonLineInfo lineInfo, string path, string message, Exception ex)
    {
        message = JsonPositionFormatMessage(lineInfo, path, message);

        return new JsonSerializationException(message, ex);
    }

    // based on internal Newtonsoft.Json.JsonPosition.FormatMessage
    internal static string JsonPositionFormatMessage(IJsonLineInfo lineInfo, string path, string message)
    {
        if (!message.EndsWith(Environment.NewLine))
        {
            message = message.Trim();

            if (!message.EndsWith(".", StringComparison.Ordinal))
                message += ".";

            message += " ";
        }

        message += string.Format(CultureInfo.InvariantCulture, "Path '{0}'", path);

        if (lineInfo != null && lineInfo.HasLineInfo())
            message += string.Format(CultureInfo.InvariantCulture, ", line {0}, position {1}", lineInfo.LineNumber, lineInfo.LinePosition);

        message += ".";

        return message;
    }
}
Rafał Kłys
źródło
1

Możesz użyć Tiny-JSON

string json = "{\"key1\":\"value1\", \"key2\":\"value2\"}";
IDictionary<string, string> dict = Tiny.Json.Decode<Dictionary<string, string>>(json);
Gering
źródło
1

Trochę za późno na grę, ale żadne z powyższych rozwiązań nie wskazało mi kierunku czystego i prostego .NET, bez rozwiązania json.net. Więc oto jest bardzo proste. Poniżej pełnego działającego przykładu tego, jak to się robi ze standardową serializacją .NET Json, przykład zawiera słownik zarówno w obiekcie głównym, jak i obiektach potomnych.

Złota kula to ten kot, parsuj ustawienia jako drugi parametr do serializatora:

DataContractJsonSerializerSettings settings =
                       new DataContractJsonSerializerSettings();
                    settings.UseSimpleDictionaryFormat = true;

Pełny kod poniżej:

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;

    namespace Kipon.dk
    {
        public class JsonTest
        {
            public const string EXAMPLE = @"{
                ""id"": ""some id"",
                ""children"": {
                ""f1"": {
                    ""name"": ""name 1"",
                    ""subs"": {
                    ""1"": { ""name"": ""first sub"" },
                    ""2"": { ""name"": ""second sub"" }
                    }
                },
                ""f2"": {
                    ""name"": ""name 2"",
                    ""subs"": {
                    ""37"": { ""name"":  ""is 37 in key""}
                    }
                }
                }
            }
            ";

            [DataContract]
            public class Root
            {
                [DataMember(Name ="id")]
                public string Id { get; set; }

                [DataMember(Name = "children")]
                public Dictionary<string,Child> Children { get; set; }
            }

            [DataContract]
            public class Child
            {
                [DataMember(Name = "name")]
                public string Name { get; set; }

                [DataMember(Name = "subs")]
                public Dictionary<int, Sub> Subs { get; set; }
            }

            [DataContract]
            public class Sub
            {
                [DataMember(Name = "name")]
                public string Name { get; set; }
            }

            public static void Test()
            {
                var array = System.Text.Encoding.UTF8.GetBytes(EXAMPLE);
                using (var mem = new System.IO.MemoryStream(array))
                {
                    mem.Seek(0, System.IO.SeekOrigin.Begin);
                    DataContractJsonSerializerSettings settings =
                       new DataContractJsonSerializerSettings();
                    settings.UseSimpleDictionaryFormat = true;

                    var ser = new DataContractJsonSerializer(typeof(Root), settings);
                    var data = (Root)ser.ReadObject(mem);
                    Console.WriteLine(data.Id);
                    foreach (var childKey in data.Children.Keys)
                    {
                        var child = data.Children[childKey];
                        Console.WriteLine(" Child: " + childKey + " " + child.Name);
                        foreach (var subKey in child.Subs.Keys)
                        {
                            var sub = child.Subs[subKey];
                            Console.WriteLine("   Sub: " + subKey + " " + sub.Name);
                        }
                    }
                }
            }
        }
    }
Kjeld Poulsen
źródło
0

Irytujące jest to, że jeśli chcesz użyć domyślnych segregatorów modelu, wygląda na to, że będziesz musiał użyć liczbowych wartości indeksu, takich jak formularz POST.

Zobacz następujący fragment tego artykułu http://msdn.microsoft.com/en-us/magazine/hh781022.aspx :

Chociaż jest to nieco sprzeczne z intuicją, żądania JSON mają takie same wymagania - one także muszą przestrzegać składni nazewnictwa formularza. Weźmy na przykład ładunek JSON dla poprzedniej kolekcji UnitPrice. Czysta składnia tablicy JSON dla tych danych byłaby reprezentowana jako:

[ 
  { "Code": "USD", "Amount": 100.00 },
  { "Code": "EUR", "Amount": 73.64 }
]

Jednak domyślni dostawcy wartości i spoiwa modeli wymagają, aby dane były reprezentowane jako post JSON:

{
  "UnitPrice[0].Code": "USD",
  "UnitPrice[0].Amount": 100.00,

  "UnitPrice[1].Code": "EUR",
  "UnitPrice[1].Amount": 73.64
}

Scenariusz kompleksowego gromadzenia obiektów jest prawdopodobnie jednym z najbardziej problematycznych scenariuszy, na które napotykają programiści, ponieważ składnia niekoniecznie jest oczywista dla wszystkich programistów. Jednak gdy nauczysz się stosunkowo prostej składni do publikowania złożonych kolekcji, scenariusze te stają się znacznie łatwiejsze do opanowania.

jeremysawesome
źródło
0

Sugeruję użycie System.Runtime.Serialization.Jsontego jest częścią .NET 4.5.

[DataContract]
public class Foo
{
   [DataMember(Name = "data")]
   public Dictionary<string,string> Data { get; set; }
}

Następnie użyj go w następujący sposób:

var serializer = new DataContractJsonSerializer(typeof(List<Foo>));
var jsonParams = @"{""data"": [{""Key"":""foo"",""Value"":""bar""}] }";
var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonParams));

var obj = serializer.ReadObject(stream);
Console.WriteLine(obj);
Dan Csharpster
źródło
Gdzie jest zdefiniowany serializator?
bnieland
..i czym jest Category3MeasureModel? Brak trafień w Google.
bnieland
1
To tylko klasa modeli, którą serializuję dla mojego projektu. To powinna być ta klasa Foo, ale skopiowałem całą sekcję z kodu produkcyjnego. Powinieneś stworzyć swój własny, jak moja klasa Foo. Dla uproszczenia zmieniłem nazwę na Foo. To tylko klasa właściwości lub pól, które mają być szeregowane do json iz powrotem.
Dan Csharpster
1
@DanCsharpster Po dokładnym skopiowaniu kodu dostaję na Windows Phone 8.1 Silverlight: `` Wystąpił wyjątek typu 'System.Security.SecurityException' w System.ServiceModel.Web.ni.dll, ale nie był obsługiwany przez użytkownika kod Informacje dodatkowe: Typ kontraktu danych „MyApp.Foo” nie może zostać przekształcony z postaci szeregowej, ponieważ element członkowski „Dane” nie jest publiczny. Upublicznienie członka naprawi ten błąd. Alternatywnie możesz ustawić go jako wewnętrzny i użyć atrybutu InternalsVisibleToAttribute w swoim zespole, aby umożliwić serializację elementów wewnętrznych
Cœur
1
@DanCsharpster Podczas zmiany właściwości Dane na członka (bez get; set;) otrzymuję: Wystąpił wyjątek pierwszej szansy typu „System.ArgumentException” w System.ServiceModel.Web.ni.dll Informacje dodatkowe: Obiekt typu „System.Object” nie można przekonwertować na typ „System.Collections.Generic.Dictionary`2 [System.String, System.String]”.
Cœur
0

Dla każdego, kto próbuje przekonwertować JSON na słownik tylko w celu odzyskania z niego pewnej wartości. istnieje prosty sposób użyciaNewtongsoft.JSON

using Newtonsoft.Json.Linq
...

JObject o = JObject.Parse(@"{
  'CPU': 'Intel',
  'Drives': [
    'DVD read/writer',
    '500 gigabyte hard drive'
  ]
}");

string cpu = (string)o["CPU"];
// Intel

string firstDrive = (string)o["Drives"][0];
// DVD read/writer

IList<string> allDrives = o["Drives"].Select(t => (string)t).ToList();
// DVD read/writer
// 500 gigabyte hard drive
wdanxna
źródło