¿Cómo puedo deserializar JSON a un diccionario simple <cadena, cadena> en ASP.NET?


679

Tengo una simple lista de clave / valor en JSON que se envía de vuelta a ASP.NET a través de POST. Ejemplo:

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

NO ESTOY TRATANDO DE DESERIALIZAR EN OBJETOS DE .NET TIPO FUERTE

Simplemente necesito un diccionario antiguo simple (de cadena, cadena) , o algún equivalente (tabla hash, diccionario (de cadena, objeto), StringDictionary de la vieja escuela - demonios, una matriz 2D de cadenas funcionaría para mí.

Puedo usar cualquier cosa disponible en ASP.NET 3.5, así como el popular Json.NET (que ya estoy usando para la serialización al cliente).

Aparentemente, ninguna de estas bibliotecas JSON tiene esta capacidad obvia de golpear la frente desde el primer momento: están totalmente enfocadas en la deserialización basada en la reflexión a través de contratos sólidos.

¿Algunas ideas?

Limitaciones:

  1. No quiero implementar mi propio analizador JSON
  2. No puedo usar ASP.NET 4.0 todavía
  3. Preferiría mantenerse alejado de la clase ASP.NET más antigua y obsoleta para JSON

1
re: limitación 3, JavaScriptSerizlizerse usa en ASP.NET MVC y ya no está en desuso.
bdukes

17
Es increíble lo difícil que fue encontrar una manera simple de convertir una cadena json en algo que pudiera usar fácilmente sin pasar por muchos flujos de pila diferentes. Es muy fácil en otros lenguajes, pero Java y C # parecen hacer todo lo posible para dificultar la vida.
user299709

Respuestas:


891

Json.NET hace esto ...

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

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

Más ejemplos: Serialización de colecciones con Json.NET


99
¿Esto también funciona cuando sus valores son enteros? ¿Se convierten automáticamente en 'cadenas'?
Highmastdon

58
@Highmastdon No, no lo hace. He encontrado que la mejor manera de deserializar en un diccionario es usar dynamiccomo tipo para los valores:JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(json);
Erik Schierboom

1
Intenté varias respuestas en esta página con un par clave / valor muy desordenado, y JSON.NET fue la única que probé que funcionó.
bnieland

15
No funciona si está utilizando una matriz de pares de valores clave en json [{key: "a", value: "1"}, {key: "b", value:"2"}], tiene que hacer algo como esto:var dict = JsonConvert.DeserializeObject<List<KeyValuePair<string, string>>>(json);
Adrian

8
Tampoco funciona si los valores son objetos anidados, porque json.net los crea como JObjects
Kugel

100

Descubrí que .NET tiene una forma integrada de convertir la cadena JSON en un Dictionary<String, Object> vía través del System.Web.Script.Serialization.JavaScriptSerializertipo en el System.Web.Extensionsensamblado 3.5 . Usa el método DeserializeObject(String).

Me topé con esto cuando hice una publicación ajax (a través de jquery) del tipo de contenido 'application / json' a un método de página estática .net y vi que el método (que tenía un solo parámetro de tipo Object) recibió mágicamente este diccionario.


55
pero el javascriptserializer incorporado es más defectuoso que json.net, esa solución es mejor. Por ejemplo, el javascriptseralizer devolverá nulos en lugar de cadenas en blanco, y no funciona en absoluto para las propiedades anulables, y así sucesivamente.
pilavdzice

1
@pilavdzice Sin mencionar la diversión que tiene cuando intenta analizar las fechas, ya que asume el formato de fecha no estándar de MS.
Básico

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

66
La ventaja del ejemplo de Nate Cook en un caso simple es evitar la necesidad de archivos DLL externos. Estoy accediendo a una API desde una consola independiente que solo puede confiar en el marco .Net.
Nick

@pilavdzice ¿Puedes entrar en más detalles sobre eso? No puedo reproducir la cosa "return null en lugar de cadenas en blanco", me dio un valor de cadena en blanco paraSomeData: ""
jrh

51

Para aquellos que buscan en Internet y se topan con esta publicación, escribí una publicación de blog sobre cómo usar la clase JavaScriptSerializer.

Leer más ... http://procbits.com/2011/04/21/quick-json-serializationdeserialization-in-c/

Aquí hay un ejemplo:

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, he probado tu solución ... Tengo json como este {"id": "13", "value": true} y para mí solo funciona la solución Diccionario <dynamic>
Marko

ok, descubrí dónde está el problema ... debes agregar [] después de la declaración del diccionario para deserializarlo correctamente ... También agrego un comentario a la publicación de tu blog ... saludos;)
Marko

He actualizado mi respuesta para reflejar su conjunto de datos específico. Funciona bien con dinámica.
JP Richardson

Acabo de escribir otro analizador JSON que es un poco más flexible y compatible con Silverlight: procbits.com/2011/08/11/…
JP Richardson

41

Intenté no usar ninguna implementación JSON externa, así que me deserialicé así:

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

66
Agregar referencias de System.Web.Extensions para usar System.Web.Script
Patrick Cullen

1
Me gusta más esta respuesta porque es simple y usa .NET System.Web.Script.Serialization. Simplemente funciona Incluso pude usar JSON "inválido" como string json = "{'id':13, 'value': true}";.
styfle

Por curiosidad, ¿existe la misma forma de deserialización en el diccionario OrdinalIgnoreCase?
Batbaatar

38

Tuve el mismo problema, así que lo escribí yo mismo. Esta solución se diferencia de otras respuestas porque puede deserializarse en múltiples niveles.

Simplemente envíe una cadena JSON para deserializar la funciónDictionary , devolverá un objeto no fuertemente tipado Dictionary<string, object>.

Código antiguo

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

Ej: Esto devolverá el Dictionary<string, object>objeto de una respuesta JSON de Facebook.

Prueba

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: la ciudad natal se deseriliza aún más en un Dictionary<string, object> objeto.

Actualizar

Mi respuesta anterior funciona muy bien si no hay una matriz en la cadena JSON. Éste se deserializa aún más List<object>si un elemento es una matriz.

Simplemente envíe una cadena JSON para deserializar la función ToDictionaryOrList , devolverá un objeto no fuertemente tipado Dictionary<string, object>o 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, gracias por señalar, hice algunas modificaciones a este código pero no lo tengo ahora. Este código no maneja objetos JArray, actualizaré el código una vez que lo tenga.
Dasun

1
No es un problema. Sólo lo menciono porque aprender acerca de la isy asoperadores en gran medida me ayudó y mi propio código simplificado.
Jordania

Funciona, pero no es eficiente, porque llama a ToString y luego Deserialize nuevamente. Mira la respuesta de Falko a continuación. Se deserializa la cadena de origen solo una vez.
Sergei Zinovyev

1
La respuesta de Falko solo funciona si conoce de antemano la estructura de datos. Esta solución puede usarse para cualquier cadena JSON.
Dasun

16

Si buscas un enfoque liviano, sin referencias agregadas, tal vez este fragmento de código que acabo de escribir funcionará (aunque no puedo garantizar la solidez al 100%).

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

[Me doy cuenta de que esto viola la Limitación OP # 1, pero técnicamente, no lo escribiste, lo hice]


3
¡Esa es la única respuesta que funciona para Silverlight y sin dependencia! Silverlight no tiene JavascriptSerializer o Serializable. Y ninguna dependencia significa que no hay Json.NET, RestSharp o MiniJSON. Solo @DanCsharpster probó otra posible solución, pero desafortunadamente no funcionó para mí como esta.
Cœur

1
¿Qué tiene de malo agregar una referencia a algo simple como JSON.NET? ¿Tiene que ser tan ligero que no puedas hacer referencia a nada? No estoy diciendo que su código no funcionará, pero cada vez que ejecuta el suyo, obviamente corre el riesgo de que su código no sea tan robusto, para casos extremos o tan rápido como una biblioteca probada como JSON.NET.
Dan Csharpster

1
Rodar el tuyo es una mala idea cuando tienes una buena alternativa. No conozco ninguna situación que tenga que ser tan ligera. Y preferiría tener un código menos óptimo que sea fácil de leer y cambiar.
Jordan

3
Originalmente escribí ese fragmento de código porque no tenía otra alternativa. Considere cosas como Silverlight, o proveedores de varios tipos para productos de Office, donde agregar referencias externas al proyecto es extremadamente problemático o imposible.
dexy

Sé que es unos años más tarde, pero esta sigue siendo una pregunta muy válida. Para cualquiera que se pregunte por qué querríamos ser tan livianos, bueno, si está trabajando con SQL CLR C #, solo hay tantas bibliotecas "seguras" que puede usar y System.RunTime.Serializationno es una de ellas, desafortunadamente JSON.NET depende de y por lo tanto tampoco se puede usar. Gracias Dexy por su excelente trabajo, me atreví a mejorarlo un poco para poder deserializar las matrices de matrices, vea el código actualizado en mi respuesta aquí .
Alberto Rechy

15

Solo necesitaba analizar un diccionario anidado , como

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

donde JsonConvert.DeserializeObjectno ayuda Encontré el siguiente enfoque:

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

El SelectTokente permite cavar hasta el campo deseado. Incluso puede especificar una ruta "x.y.z"para descender más al objeto JSON.


JObject.Parse (json) .ToObject <Diccionario <Guid, List <int> >> () funcionó para mí en mi escenario gracias
geedubb

11

System.Text.Json

Esto ahora se puede hacer usando el System.Text.Jsonque está integrado en .net core 3.0. Ahora es posible deserializar JSON sin usar bibliotecas de terceros.

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

También disponible en el paquete nu-get System.Text.Json si se usa .Net Standard o .Net Framework.


1
¡Si! System.Text.JsonEs el camino a seguir en estos días.
mfluehr

2
¡Sí, parece prometedor! Sin embargo, tenga en cuenta que la versión predeterminada de System.Text.Json de .NET Core 3.1 no admite la deserialización de diccionarios con teclas que no sean cadenas. Si bien mi OP era sobre cadenas, en la práctica ahora, tengo muchas teclas Guid, por lo que esto me "mordió" al intentar hacer el cambio. Tampoco tiene equivalentes de algunos de los atributos (obligatorios, etc.).
richardtallent

6

Mark Rendle publicó esto como un comentario , quería publicarlo como respuesta ya que es la única solución que ha funcionado hasta ahora para devolver el éxito y los resultados de los códigos de error json de la respuesta reCaptcha de Google.

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

Gracias de nuevo, Mark!


1
El JavaScriptSerializer ha quedado casi en desuso. La documentación dice que deberíamos usar JSON.NET ( docs.microsoft.com/en-us/dotnet/api/… )
Mario Lopez

También es bueno para las aplicaciones de formularios web heredados donde no desea incluir dependencias adicionales.
Andrew Grothe

5

Editar: Esto funciona, pero la respuesta aceptada usando Json.NET es mucho más sencilla. Dejar este en caso de que alguien necesite un código solo BCL.

No es compatible con .NET Framework de fábrica. Una deslumbrante supervisión: no todos necesitan deserializarse en objetos con propiedades con nombre. Así que terminé rodando el mío:

<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

Llamado con:

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

Perdón por la combinación de C # y VB.NET ...


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

1
Esto es fantástico. Ayuda con las implementaciones de servicios WCF que interactúan usando JSON con clientes basados ​​en navegador.
Anton

@ Mark Rendle: Su implementación es muuuy simple, y es la ÚNICA que me ha funcionado hasta ahora para obtener los resultados json tanto de éxito como de códigos de error. He probado muchas soluciones, así que gracias por publicar eso como un comentario. Debería ser la respuesta.
Bryan

5

He agregado el código enviado por jSnake04 y Dasun aquí. He agregado código para crear listas de objetos a partir de JArrayinstancias. Tiene una recursión bidireccional, pero como funciona en un modelo de árbol fijo finito, no hay riesgo de desbordamiento de la pila a menos que los datos sean masivos.

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

Agregué una verificación de valores nulos en el JSON a la otra respuesta

Tuve el mismo problema, así que escribí esto yo mismo. Esta solución se diferencia de otras respuestas porque puede deserializarse en múltiples niveles.

Simplemente envíe una cadena json para deserializar la funciónDictionary; devolverá un objeto no fuertemente tipado 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;
}

Ej: Esto devolverá el Dictionary<string, object>objeto de una respuesta JSON de 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: la ciudad natal se deserializa aún más en un Dictionary<string, object>objeto.


1
+1 Como dije con Dasun arriba. Podrías comprobar si d.Value is JObject. No tiene que pasar por la reflexión para verificar los tipos. Y con el isoperador no necesita verificar nulo. Devuelve falso si el objeto es nulo.
Jordan

3

Parece que todas estas respuestas aquí solo asumen que puedes sacar esa pequeña cadena de un objeto más grande ... para las personas que buscan simplemente deserealizar un objeto grande con un diccionario de este tipo en algún lugar dentro del mapeo, y que están usando el System.Runtime.Serialization.Jsonsistema DataContract, aquí está una solución:

Una respuesta en gis.stackexchange.com tenía este interesante enlace . Tuve que recuperarlo con archive.org, pero ofrece una solución bastante perfecta: una costumbreIDataContractSurrogate clase en la que implementas exactamente tus propios tipos. Pude expandirlo fácilmente.

Sin embargo, hice un montón de cambios. Como la fuente original ya no está disponible, publicaré toda la clase aquí:

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 agregar nuevos tipos compatibles a la clase, solo necesita agregar su clase, asignarle los constructores y funciones correctos (vea SurrogateDictionaryun ejemplo), asegúrese de que herede JsonSurrogateObjecty agregue su asignación de tipos al KnownTypesdiccionario. El SurrogateDictionary incluido puede servir como base para cualquier Dictionary<String,T>tipo en el que T sea cualquier tipo que se deserialice correctamente.

Llamarlo es realmente simple:

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

Tenga en cuenta que, por alguna razón, esto tiene problemas para usar cadenas de teclas que contienen espacios; simplemente no estaban presentes en la lista final. Podría ser simplemente que está en contra de las especificaciones json y la API que estaba llamando estaba mal implementada, eso sí; No se. De todos modos, resolví esto regex-reemplazándolos con guiones bajos en los datos json sin procesar y arreglando el diccionario después de la deserialización.


Por cierto, por alguna razón peculiar, Mono parece tener problemas para ejecutar estas cosas ...
Nyerguds

Gracias por compartir, desafortunadamente esta solución no admite tipos no primitivos, y no hay forma de obtener el valor bruto, por lo que puede construirlo usted mismo. Si registro mi tipo personalizado en KnownTypes y lo uso en el diccionario, primero llama al diccionario, esperaría que comience a analizar de abajo hacia arriba desde los tipos más remotos hasta los más complejos.
Ivan Leonenko

Bueno, la pregunta solo preguntaba Dictionary<String,String>. Sinceramente, nunca intenté deserializar tipos complejos con este sistema.
Nyerguds

3

Según los comentarios anteriores, intenteJsonConvert.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 incluso para objetos complejos y listas.


1

Acabo de implementar esto en RestSharp . Esta publicación me fue útil.

Además del código en el enlace, aquí está mi código. Ahora obtengo algunos Dictionaryresultados cuando hago algo como esto:

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

Tenga en cuenta el tipo de JSON que espera: en mi caso, estaba recuperando un solo objeto con varias propiedades. En el enlace adjunto, el autor estaba recuperando una lista.


1

Mi enfoque se deserializa directamente a IDictionary, sin JObject o ExpandObject en el medio. El código usa el convertidor, que básicamente se copia de la clase ExpandoObjectConverter que se encuentra en el código fuente JSON.NET, pero usando IDictionary en lugar de 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

Podrías usar Tiny-JSON

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

1

Un poco tarde para el juego, pero ninguna de las soluciones anteriores me señaló en la dirección de una solución pura y simple .NET, no json.net. Así que aquí está, terminó siendo muy simple. Debajo de un ejemplo completo de cómo se hace con la serialización .NET Json estándar, el ejemplo tiene un diccionario tanto en el objeto raíz como en los objetos secundarios.

La bala de oro es este gato, analiza la configuración como segundo parámetro para el serializador:

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

Código completo a continuación:

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

De manera bastante molesta, si desea usar las carpetas de modelo predeterminadas, parece que tendrá que usar valores de índice numérico como un formulario POST.

Vea el siguiente extracto de este artículo http://msdn.microsoft.com/en-us/magazine/hh781022.aspx :

Aunque es algo contradictorio, las solicitudes JSON tienen los mismos requisitos: también deben cumplir con la sintaxis de los nombres posteriores al formulario. Tome, por ejemplo, la carga útil de JSON para la colección anterior de UnitPrice. La sintaxis de matriz JSON pura para estos datos se representaría como:

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

Sin embargo, los proveedores de valores predeterminados y las carpetas de modelos requieren que los datos se representen como una publicación de formulario JSON:

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

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

El complejo escenario de recopilación de objetos es quizás uno de los escenarios más problemáticos que enfrentan los desarrolladores porque la sintaxis no es necesariamente evidente para todos los desarrolladores. Sin embargo, una vez que aprenda la sintaxis relativamente simple para publicar colecciones complejas, estos escenarios se vuelven mucho más fáciles de manejar.


0

Sugeriría usar System.Runtime.Serialization.Jsoneso es parte de .NET 4.5.

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

Entonces úsalo así:

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

¿Dónde se define el serializador?
bnieland

¿Y qué es un Category3MeasureModel? No hay éxitos en Google.
bnieland

1
Esa es solo la clase de modelo que estoy serializando para mi proyecto. Se supone que es esa clase de Foo, pero volví a copiar toda la sección del código de producción. Deberías crear el tuyo, como mi clase Foo. Lo renombré a Foo para hacerlo más simple. Es solo una clase de las propiedades o campos que desea serializar a json y viceversa.
Dan Csharpster

1
@DanCsharpster Con una copia exacta de su código, obtengo, en Windows Phone 8.1 Silverlight: `Se produjo una excepción del tipo 'System.Security.SecurityException' en System.ServiceModel.Web.ni.dll pero no se manejó en el usuario código Información adicional: El tipo de contrato de datos 'MyApp.Foo' no se puede deserializar porque el miembro 'Datos' no es público. Hacer público al miembro solucionará este error. Alternativamente, puede hacerlo interno y usar el atributo InternalsVisibleToAttribute en su ensamblaje para permitir la serialización de miembros internos
Cœur

1
@DanCsharpster Y al cambiar la propiedad Datos para que sea miembro (sin get; set;), obtengo: Se produjo una excepción de primera oportunidad del tipo 'System.ArgumentException' en System.ServiceModel.Web.ni.dll Información adicional: Objeto de el tipo 'System.Object' no se puede convertir al tipo 'System.Collections.Generic.Dictionary`2 [System.String, System.String]'.
Cœur

0

Para cualquiera que esté tratando de convertir JSON a diccionario solo por recuperar algún valor. hay una manera simple 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
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.