Como posso desserializar JSON para um dicionário simples <string, string> no ASP.NET?


679

Eu tenho uma lista simples de chave / valor no JSON sendo enviada de volta ao ASP.NET via POST. Exemplo:

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

NÃO ESTOU TENTANDO DESERIALIZAR EM OBJETOS .NET FORMAMENTE TIPOS

Eu simplesmente preciso de um dicionário antigo (Of String, String) ou equivalente (tabela de hash, Dictionary (Of String, Object), StringDictionary da velha escola - inferno, uma matriz 2D de strings funcionaria para mim.

Eu posso usar qualquer coisa disponível no ASP.NET 3.5, bem como a Json.NET popular (que eu já estou usando para serialização para o cliente).

Aparentemente, nenhuma dessas bibliotecas JSON tem essa capacidade óbvia de bater na testa imediatamente - elas são totalmente focadas na desserialização baseada em reflexão por meio de contratos firmes.

Alguma ideia?

Limitações:

  1. Não quero implementar meu próprio analisador JSON
  2. Ainda não é possível usar o ASP.NET 4.0
  3. Prefere ficar longe da classe ASP.NET mais antiga e obsoleta para JSON

1
re: limitation 3, JavaScriptSerizlizeré usado no ASP.NET MVC e não é mais descontinuado.
Bdukes

17
é incrível como foi difícil encontrar uma maneira simples de converter uma string json em algo que eu pudesse usar facilmente sem percorrer muitos fluxos de stackover diferentes. É tão fácil em outras linguagens, mas Java e C # parecem se esforçar para dificultar a vida.
precisa saber é o seguinte

Respostas:


891

O Json.NET faz isso ...

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

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

Mais exemplos: serializando coleções com o Json.NET


9
Isso também funciona quando os valores são inteiros. Eles são convertidos automaticamente para 'strings'?
Highmastdon

58
@ Highmastdon Não, não. Eu descobri que a melhor maneira de desserializar em um dicionário é usar dynamiccomo o tipo para os valores:JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(json);
Erik Schierboom

1
Tentei várias respostas nesta página com um par de chave / valor muito confuso, e o JSON.NET foi o único que tentei que funcionou.
bnieland

15
Não funciona se você estiver usando uma matriz de pares de valores-chave em JSON [{key: "a", value: "1"}, {key: "b", value:"2"}]você tem que fazer algo como isto:var dict = JsonConvert.DeserializeObject<List<KeyValuePair<string, string>>>(json);
Adrian

8
Também não funciona se os valores são objetos aninhados, porque json.net cria aqueles como JObjects
Kugel

100

Eu descobri .NET foi construído em forma de lançar a string JSON em uma Dictionary<String, Object>via do System.Web.Script.Serialization.JavaScriptSerializertipo no 3,5 System.Web.Extensionsmontagem. Use o método DeserializeObject(String).

Eu me deparei com isso ao fazer uma postagem do ajax (via jquery) do tipo de conteúdo 'application / json' em um método de página .net estático e vi que o método (que tinha um único parâmetro do tipo Object) recebeu magicamente esse dicionário.


5
mas o javascriptserializer incorporado é mais complicado que o json.net, essa solução é melhor. Por exemplo, o javascriptseralizer retornará nulos em vez de cadeias em branco, e não funcionará de maneira alguma para propriedades anuláveis, e assim por diante.
Pilavdzice

1
@pilavdzice Sem mencionar a diversão que você tem ao tentar analisar datas, pois assume o formato de data fora do padrão da MS.
Básico

16
Exemplo de código rápido: var jsSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();seguido porDictionary<string, object> dict = (Dictionary<string, object>)jsSerializer.DeserializeObject(jsonString);
Nate Cook

6
A vantagem do exemplo de Nate Cook em um caso simples é evitar a necessidade de DLLs externas. Estou acessando uma API a partir de um console autônomo que pode confiar apenas na estrutura .Net.
Nick.T

@pilavdzice Você pode entrar em mais detalhes sobre isso? Eu não posso reproduzir o "nulo retorno em vez de cordas em branco" coisa, ele me deu um valor de string em branco paraSomeData: ""
JRH

51

Para aqueles que pesquisam na Internet e se deparam com este post, escrevi um post sobre como usar a classe JavaScriptSerializer.

Leia mais ... http://procbits.com/2011/04/21/quick-json-serializationdeserialization-in-c/

Aqui está um exemplo:

var json = "{\"id\":\"13\", \"value\": true}";
var jss = new JavaScriptSerializer();
var table = jss.Deserialize<dynamic>(json);
Console.WriteLine(table["id"]);
Console.WriteLine(table["value"]);

hm, eu tentei sua solução ... Eu tenho json assim {"id": "13", "value": true} e somente para mim, a solução Dictionary <dynamic> funciona
Marko

ok, encontrei onde está o problema ... você precisa adicionar [] após a declaração do dicionário para desserializar corretamente ... também estou adicionando comentários ao seu blog ... felicidades;);
Marko

Atualizei minha resposta para refletir seu conjunto de dados específico. Funciona bem com dinâmico.
JP Richardson

Eu só escreveu outro analisador JSON que é um pouco mais flexível e suporta Silverlight: procbits.com/2011/08/11/...
JP Richardson

41

Tentei não usar nenhuma implementação JSON externa, então desserializou assim:

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);

6
Adicione referência System.Web.Extensions para usar System.Web.Script
Patrick Cullen

1
Eu gosto mais desta resposta porque é simples e usa o .NET System.Web.Script.Serialization. Isso simplesmente funciona. Consegui até usar JSON "inválido" string json = "{'id':13, 'value': true}";.
Styfle

Por curiosidade, existe a mesma maneira de desserializar para o dicionário OrdinalIgnoreCase?
batbaatar

38

Eu tinha o mesmo problema, então escrevi isso sozinho. Essa solução é diferenciada de outras respostas porque pode desserializar para vários níveis.

Basta enviar a string JSON para a função deserializeToDictionary , que retornará um Dictionary<string, object>objeto não fortemente tipado .

Código antigo

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;
}

Ex: Isso retornará o Dictionary<string, object>objeto de uma resposta JSON do Facebook.

Teste

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);
}

Nota: a cidade natal deseriliza ainda mais em um Dictionary<string, object> objeto.

Atualizar

Minha resposta antiga funciona muito bem se não houver uma matriz na string JSON. Este ainda desserializa para a List<object>se um elemento é uma matriz.

Basta enviar uma string JSON para a função deserializeToDictionaryOrList que retornará um Dictionary<string, object>objeto ou tipo não fortemente tipado 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;
    }
}

@ Jordan obrigado por apontar, fiz algumas modificações neste código, mas não o tenho agora. Este código não manipula objetos JArray, atualizarei o código assim que o tiver.
Dasun

1
Não é um problema. Eu só mencionei isso porque aprender sobre o ise asoperadores muito me ajudou e simplificado meu próprio código.
Jordânia

Funciona, mas não é eficiente, porque chama ToString e desserializa novamente. Veja a resposta de Falko abaixo. É desserializada a cadeia de origem apenas uma vez.
Sergei Zinovyev

1
A resposta de Falko funciona apenas se você conhecer a estrutura de dados com antecedência. Esta solução pode ser usada para qualquer sequência JSON.
Dasun

16

Se você está buscando um tipo de abordagem leve e sem adição de referências, talvez esse código que acabei de escrever funcione (embora eu não possa 100% garantir robustez).

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))));
}

[Eu percebo que isso viola a limitação nº 1 do OP, mas tecnicamente você não escreveu, eu escrevi]


3
Essa é a única resposta para o Silverlight e sem dependência! O Silverlight não possui JavascriptSerializer ou Serializable. E nenhuma dependência significa não Json.NET, RestSharp ou MiniJSON. Apenas o @DanCsharpster tentou outra solução possível, mas infelizmente não estava funcionando para mim como este.
Cœur

1
O que há de errado em adicionar uma referência a algo simples como o JSON.NET? Precisa ser tão leve que você não possa fazer referência a nada? Não estou dizendo que seu código não funcione, mas sempre que você cria o seu, você obviamente corre o risco de não ser tão robusto, para coisas como casos extremos, ou rápido como uma biblioteca testada como JSON.NET.
Dan Csharpster 28/08

1
Rolar o seu próprio é uma má idéia quando você tem uma boa alternativa. Não conheço nenhuma situação que deva ser tão leve. E eu preferiria ter um código menos ideal, fácil de ler e alterar.
Jordânia

3
Originalmente, escrevi esse código porque não tinha alternativa. Considere coisas como o Silverlight, ou fornecedores de vários tipos de produtos do Office, onde adicionar referências externas ao projeto é extremamente problemático ou impossível.
dexy

Eu sei que é alguns anos depois, mas essa ainda é uma pergunta muito válida. Para alguém que se pergunta por que gostaríamos de ser tão leves, bem, se você estiver trabalhando com o SQL CLR C #, há apenas tantas bibliotecas "seguras" que você pode usar e System.RunTime.Serializationnão é uma delas, infelizmente o JSON.NET depende de e, portanto, também não pode ser usado. Obrigado a Dexy pelo seu excelente trabalho, ousei aprimorá-lo um pouco para poder desserializar matrizes de matrizes, veja o código atualizado na minha resposta aqui .
Alberto Rechy

15

Eu só precisava analisar um dicionário aninhado , como

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

onde JsonConvert.DeserializeObjectnão ajuda. Eu encontrei a seguinte abordagem:

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

O SelectTokenpermite cavar para o campo desejado. Você pode até especificar um caminho "x.y.z"para avançar ainda mais no objeto JSON.


JObject.Parse (JSON) .ToObject <Dictionary <Guid, List <int >>> () trabalhou para mim no meu cenário graças
geedubb

11

System.Text.Json

Agora isso pode ser feito usando o System.Text.Jsonque está embutido em .net core 3.0. Agora é possível desserializar JSON sem usar bibliotecas de terceiros.

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

Também disponível no pacote nu-get System.Text.Json, se estiver usando o .Net Standard ou o .Net Framework.


1
Sim! System.Text.Jsoné o caminho a percorrer nos dias de hoje.
mfluehr

2
Sim, parece promissor! No entanto, observe que a versão padrão do System.Text.Json do .NET Core 3.1 não oferece suporte à desserialização de dicionários com chaves não de seqüência de caracteres. Enquanto meu OP era sobre strings, na prática agora, eu tenho muitas teclas Guid, então isso me "mordeu" ao tentar fazer a troca. Também não possui equivalentes de alguns dos atributos (obrigatório, etc.).
Richardtallent

6

Mark Rendle postou isso como um comentário , queria postá-lo como resposta, já que é a única solução que funcionou até agora para retornar o sucesso e os resultados dos códigos de erro json da resposta do Google reCaptcha.

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

Mais uma vez obrigado, Mark!


1
O JavaScriptSerializer foi quase obsoleto. A documentação diz que devemos estar usando JSON.NET ( docs.microsoft.com/en-us/dotnet/api/... )
Mario Lopez

Também é bom para aplicativos de formulários da web herdados, nos quais você não deseja incluir dependências adicionais.
Andrew Grothe

5

Edit: Isso funciona, mas a resposta aceita usando o Json.NET é muito mais direta. Deixando este caso alguém precise de código somente BCL.

Não é suportado pela estrutura .NET imediatamente. Uma supervisão flagrante - nem todo mundo precisa desserializar em objetos com propriedades nomeadas. Então acabei fazendo o meu próprio:

<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

Chamado com:

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"]);

Desculpe pela mistura de C # e VB.NET…


2
[TestMethod] public void TestSimpleObject () {const string json = @ "{" "Nome" ":" "Bob" "," "Idade" ": 42}"; var dict = new JavaScriptSerializer (). DeserializeObject (json) como IDictionary <string, object>; Assert.IsNotNull (dict); Assert.IsTrue (dict.ContainsKey ("Name")); Assert.AreEqual ("Bob", dict ["Name"]); Assert.IsTrue (dict.ContainsKey ("Age")); Assert.AreEqual (42, ditado ["Idade"]); }
Mark Rendle 22/09

1
Isto é fantástico. Ajuda com implementações de serviço WCF que fazem interface usando JSON com clientes baseados em navegador.
Anton

@ Mark Rendle: Sua implementação é muito simples e é a ÚNICA que funcionou para mim até agora na obtenção dos resultados json dos códigos de erro e de sucesso. Eu tentei muitas soluções, então obrigado por postar isso como um comentário. Deve ser a resposta.
21715 Bryan

5

Eu adicionei o código enviado por jSnake04 e Dasun aqui. Adicionei código para criar listas de objetos a partir de JArrayinstâncias. Possui recursão bidirecional, mas, como está funcionando em um modelo de árvore finito e fixo, não há risco de estouro de pilha, a menos que os dados sejam massivos.

/// <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;
}

4

Adicionei uma verificação de valores nulos no JSON para a outra resposta

Eu tive o mesmo problema, então eu escrevi isso sozinho. Essa solução é diferenciada de outras respostas porque pode desserializar para vários níveis.

Basta enviar a string json para a função deserializeToDictionary , que retornará um Dictionary<string, object>objeto não fortemente tipado .

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;
}

Ex: Isso retornará o Dictionary<string, object>objeto de uma resposta JSON do Facebook.

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);
}

Nota: a cidade natal é desserializada ainda mais em um Dictionary<string, object>objeto.


1
+1 Como eu disse com Dasun acima. Você pode apenas verificar se d.Value is JObject. Você não precisa passar pela reflexão para verificar os tipos. E com o isoperador, você não precisa verificar se é nulo. Retorna false se o objeto for nulo.
Jordânia

3

Parece que todas essas respostas aqui assumem que você pode obter essa pequena string de um objeto maior ... para pessoas que procuram simplesmente desserrealizar um objeto grande com esse dicionário em algum lugar do mapeamento e que estão usando o System.Runtime.Serialization.Jsonsistema DataContract, aqui está uma solução:

Uma resposta em gis.stackexchange.com tinha esse link interessante . Eu tive que recuperá-lo com o archive.org, mas ele oferece uma solução praticamente perfeita: uma IDataContractSurrogateclasse personalizada na qual você implementa exatamente seus próprios tipos. Consegui expandi-lo facilmente.

Fiz várias alterações, no entanto. Como a fonte original não está mais disponível, postarei a turma inteira aqui:

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
    }
}

Para adicionar novos tipos suportados à classe, basta adicionar sua classe, fornecer os construtores e funções corretos (veja SurrogateDictionaryum exemplo), certificar-se de que ela seja herdada JsonSurrogateObjecte adicionar seu mapeamento de tipo ao KnownTypesdicionário. O SurrogateDictionary incluído pode servir como base para qualquer Dictionary<String,T>tipo em que T é qualquer tipo que desserializa corretamente.

Chamá-lo é realmente simples:

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

Observe que, por algum motivo, essa coisa tem problemas ao usar cadeias de teclas que contêm espaços; eles simplesmente não estavam presentes na lista final. Pode ser que seja simplesmente contra especificações do json e a API que eu estava chamando foi mal implementada, lembre-se; Não sei. Enfim, resolvi isso substituindo-os por expressões regulares nos dados brutos do json e corrigindo o dicionário após a desserialização.


By the way, por algum motivo peculiar Mono parece ter problemas para executar essas coisas ...
Nyerguds

Obrigado por compartilhar, infelizmente, esta solução não suporta tipos não primitivos e não há como obter o valor bruto, para que você possa construí-lo. Se eu registrar meu tipo personalizado em KnownTypes e usá-lo no dicionário, ele chama o dicionário primeiro, esperaria que ele comece a analisar de baixo para cima, dos tipos mais remotos aos mais complexos.
Ivan Leonenko 23/07/19

Bem, a pergunta só foi feita Dictionary<String,String>. Sinceramente, nunca tentei desserializar tipos complexos com esse sistema.
Nyerguds 23/07/19

3

Com base nos comentários acima, tenteJsonConvert.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);

parece funcionar mesmo para objetos e listas complexos.


1

Acabei de implementar isso no RestSharp . Este post foi útil para mim.

Além do código no link, aqui está o meu código. Agora, recebo um Dictionaryresultado quando faço algo assim:

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>>();

Esteja atento ao tipo de JSON que você espera - no meu caso, eu estava recuperando um único objeto com várias propriedades. No link anexado, o autor estava recuperando uma lista.


1

Minha abordagem desserializa diretamente o IDictionary, sem JObject ou ExpandObject no meio. O código usa o conversor, que é basicamente copiado da classe ExpandoObjectConverter encontrada no código-fonte JSON.NET, mas usando o IDictionary em vez do ExpandoObject.

Uso:

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

Código:

// 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;
    }
}

1

Você poderia usar o Tiny-JSON

string json = "{\"key1\":\"value1\", \"key2\":\"value2\"}";
IDictionary<string, string> dict = Tiny.Json.Decode<Dictionary<string, string>>(json);

1

Um pouco atrasado para o jogo, mas nenhuma das soluções acima me apontou na direção de um .NET puro e simples, nenhuma solução json.net. Então aqui está, acabou sendo muito simples. Abaixo um exemplo completo de como isso é feito com a serialização .NET Json padrão, o exemplo possui dicionário no objeto raiz e nos objetos filho.

A bala de ouro é esse gato, analise as configurações como segundo parâmetro do serializador:

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

Código completo abaixo:

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);
                        }
                    }
                }
            }
        }
    }

0

Irritantemente, se você deseja usar os fichários de modelo padrão, parece que você precisará usar valores de índice numérico como um formulário POST.

Consulte o seguinte trecho deste artigo http://msdn.microsoft.com/en-us/magazine/hh781022.aspx :

Embora seja um pouco contra-intuitivo, as solicitações JSON têm os mesmos requisitos - elas também devem seguir a sintaxe de pós-nomeação do formulário. Pegue, por exemplo, a carga útil JSON da coleção UnitPrice anterior. A sintaxe pura da matriz JSON para esses dados seria representada como:

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

No entanto, os provedores de valor padrão e os binders de modelo exigem que os dados sejam representados como uma postagem de formulário JSON:

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

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

O cenário complexo de coleta de objetos é talvez um dos cenários mais problemáticos que os desenvolvedores enfrentam porque a sintaxe não é necessariamente evidente para todos os desenvolvedores. No entanto, depois de aprender a sintaxe relativamente simples para postar coleções complexas, esses cenários se tornam muito mais fáceis de lidar.


0

Eu sugeriria usar System.Runtime.Serialization.Jsonisso faz parte do .NET 4.5.

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

Em seguida, use-o assim:

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);

Onde o serializador está definido?
bnieland

..e o que é um modelo Category3Measure? Nenhum resultado no Google.
bnieland

1
Essa é apenas a classe de modelo que estou serializando para o meu projeto. Deveria ser a classe Foo, mas recopiei a seção inteira do código de produção. Você deve criar o seu próprio, como a minha classe Foo. Renomeei para Foo para simplificar. É apenas uma classe das propriedades ou campos que você deseja serializar para json e vice-versa.
Dan Csharpster 27/03

1
@DanCsharpster Com uma cópia exata do seu código, recebo, no Windows Phone 8.1 Silverlight: `Uma exceção do tipo 'System.Security.SecurityException' ocorreu no System.ServiceModel.Web.ni.dll, mas não foi tratada pelo usuário código Informações adicionais: O tipo de contrato de dados 'MyApp.Foo' não pode ser desserializado porque o membro 'Data' não é público. Tornar o membro público corrigirá esse erro. Alternativamente, você pode torná-lo interna e usar o atributo InternalsVisibleToAttribute no seu conjunto, a fim de permitir a serialização de membros internos
Cœur

1
@DanCsharpster E ao alterar a propriedade Data para ser um membro (sem get; set;), recebo: Uma primeira chance de exceção do tipo 'System.ArgumentException' ocorreu em System.ServiceModel.Web.ni.dll Informações adicionais: Objeto de o tipo 'System.Object' não pode ser convertido no tipo 'System.Collections.Generic.Dictionary`2 [System.String, System.String]'.
Cœur

0

Para quem está tentando converter JSON em dicionário apenas para recuperar algum valor dele. existe uma maneira simples de usarNewtongsoft.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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.