Deserialización de datos JSON a C # usando JSON.NET

144

Soy relativamente nuevo en trabajar con datos de C # y JSON y estoy buscando orientación. Estoy usando C # 3.0, con .NET3.5SP1 y JSON.NET 3.5r6.

Tengo una clase de C # definida que necesito completar desde una estructura JSON. Sin embargo, no todas las estructuras JSON para una entrada que se recupera del servicio web contienen todos los atributos posibles que se definen dentro de la clase C #.

He estado haciendo lo que parece ser el camino incorrecto, difícil y simplemente seleccionando cada valor uno por uno del JObject y transformando la cadena en la propiedad de clase deseada.

JsonSerializer serializer = new JsonSerializer();
var o = (JObject)serializer.Deserialize(myjsondata);

MyAccount.EmployeeID = (string)o["employeeid"][0];

¿Cuál es la mejor manera de deserializar una estructura JSON en la clase C # y manejar los posibles datos faltantes de la fuente JSON?

Mi clase se define como:

  public class MyAccount
  {

    [JsonProperty(PropertyName = "username")]
    public string UserID { get; set; }

    [JsonProperty(PropertyName = "givenname")]
    public string GivenName { get; set; }

    [JsonProperty(PropertyName = "sn")]
    public string Surname { get; set; }

    [JsonProperty(PropertyName = "passwordexpired")]
    public DateTime PasswordExpire { get; set; }

    [JsonProperty(PropertyName = "primaryaffiliation")]
    public string PrimaryAffiliation { get; set; }

    [JsonProperty(PropertyName = "affiliation")]
    public string[] Affiliation { get; set; }

    [JsonProperty(PropertyName = "affiliationstatus")]
    public string AffiliationStatus { get; set; }

    [JsonProperty(PropertyName = "affiliationmodifytimestamp")]
    public DateTime AffiliationLastModified { get; set; }

    [JsonProperty(PropertyName = "employeeid")]
    public string EmployeeID { get; set; }

    [JsonProperty(PropertyName = "accountstatus")]
    public string AccountStatus { get; set; }

    [JsonProperty(PropertyName = "accountstatusexpiration")]
    public DateTime AccountStatusExpiration { get; set; }

    [JsonProperty(PropertyName = "accountstatusexpmaxdate")]
    public DateTime AccountStatusExpirationMaxDate { get; set; }

    [JsonProperty(PropertyName = "accountstatusmodifytimestamp")]
    public DateTime AccountStatusModified { get; set; }

    [JsonProperty(PropertyName = "accountstatusexpnotice")]
    public string AccountStatusExpNotice { get; set; }

    [JsonProperty(PropertyName = "accountstatusmodifiedby")]
    public Dictionary<DateTime, string> AccountStatusModifiedBy { get; set; }

    [JsonProperty(PropertyName = "entrycreatedate")]
    public DateTime EntryCreatedate { get; set; }

    [JsonProperty(PropertyName = "entrydeactivationdate")]
    public DateTime EntryDeactivationDate { get; set; }

  }

Y una muestra del JSON para analizar es:

{
    "givenname": [
        "Robert"
    ],
    "passwordexpired": "20091031041550Z",
    "accountstatus": [
        "active"
    ],
    "accountstatusexpiration": [
        "20100612000000Z"
    ],
    "accountstatusexpmaxdate": [
        "20110410000000Z"
    ],
    "accountstatusmodifiedby": {
        "20100214173242Z": "tdecker",
        "20100304003242Z": "jsmith",
        "20100324103242Z": "jsmith",
        "20100325000005Z": "rjones",
        "20100326210634Z": "jsmith",
        "20100326211130Z": "jsmith"
    },
    "accountstatusmodifytimestamp": [
        "20100312001213Z"
    ],
    "affiliation": [
        "Employee",
        "Contractor",
        "Staff"
    ],
    "affiliationmodifytimestamp": [
        "20100312001213Z"
    ],
    "affiliationstatus": [
        "detached"
    ],
    "entrycreatedate": [
        "20000922072747Z"
    ],
    "username": [
        "rjohnson"
    ],
    "primaryaffiliation": [
        "Staff"
    ],
    "employeeid": [
        "999777666"
    ],
    "sn": [
        "Johnson"
    ]
}
Govind Malviya
fuente

Respuestas:

76

¿Has intentado usar el método genérico DeserializeObject?

JsonConvert.DeserializeObject<MyAccount>(myjsondata);

Cualquier campo faltante en los datos JSON simplemente debe dejarse NULL.

ACTUALIZAR:

Si la cadena JSON es una matriz, intente esto:

var jarray = JsonConvert.DeserializeObject<List<MyAccount>>(myjsondata);

jarrayentonces debería ser a List<MyAccount>.

OTRA ACTUALIZACIÓN:

La excepción que está obteniendo no es consistente con una variedad de objetos: creo que el serializador está teniendo problemas con su accountstatusmodifiedbypropiedad de diccionario .

Intente excluir la accountstatusmodifiedby propiedad de la serialización y vea si eso ayuda. Si lo hace, es posible que deba representar esa propiedad de manera diferente.

Documentación: Serialización y deserialización de JSON con Json.NET

Dave Swersky
fuente
Gracias. Sin embargo, aparece el error "No se puede deserializar la matriz JSON en el tipo 'System.String'". cuando intenta deserializar (por ejemplo) la matriz JSON givenname en la cadena de clase GivenName. Los atributos JSON que he definido como cadena en la clase C # son solo matrices de elementos individuales. Es por eso que comencé a elegir los valores uno por uno a medida que me encontraba con este tipo de problema durante el proceso de deserialización. Otra magia que estoy pasando por alto?
Entonces ... DateTime AccountStatusExpiration(por ejemplo) no es anulable como se define en el código. ¿Qué se necesitaría para hacerlo anulable? Simplemente cambie DateTimea DateTime??
Hamish Grubijan
50

Respuesta reproducida de https://stackoverflow.com/a/10718128/776476

Puede usar el dynamictipo C # para facilitar las cosas. Esta técnica también simplifica la refactorización, ya que no se basa en cadenas mágicas.

Json

La jsonsiguiente cadena es una respuesta simple de una llamada http api y define dos propiedades: Idy Name.

{"Id": 1, "Name": "biofractal"}

C#

Use JsonConvert.DeserializeObject<dynamic>()para deserializar esta cadena en un tipo dinámico y luego simplemente acceda a sus propiedades de la manera habitual.

var results = JsonConvert.DeserializeObject<dynamic>(json);
var id = results.Id;
var name= results.Name;

Nota : El enlace NuGet para el ensamblaje NewtonSoft es http://nuget.org/packages/newtonsoft.json . No olvides agregar: using Newtonsoft.Json;para acceder a esas clases.

biofractal
fuente
77
Me encanta el uso de <dynamic>. Sin embargo, tuve que hacer esto para que funcionara: resultado ["Id"]. Valor y resultado ["Nombre"]. Valor
fredw
1
Aunque dinámico es una buena alternativa, los ejemplos anteriores no usan 'cadenas mágicas' sino que usan genéricos fuertemente tipados que se actualizan durante las técnicas normales de refactorización de VS (a menos que estén dentro de una Vista en MVC que esté fuera del alcance de esta pregunta).
gcoleman0828
44
Todavía tienes 'cadenas mágicas', ¡ahora están ocultas por el uso de la dinámica!
Ian Ringrose
De hecho, el biofractal lo tiene al revés en "cuerdas mágicas", como señala @ IanRingrose. El objeto dinámico devuelto por DeserializeObject<dynamic>es, por definición, no "verificado por tipo". Por lo que las siguientes líneas results.Idy results.Nameno se puede comprobar en tiempo de compilación. Cualquier error ocurrirá en tiempo de ejecución en su lugar. Compare esto con una llamada fuertemente tipada como DeserializeObject<List<MyAccount>>. Las referencias a esas propiedades se pueden confirmar en tiempo de compilación. El uso de dynamic, aunque puede ser conveniente, introduce "cadenas mágicas" como nombres de propiedad; una reducción en la robustez en mi humilde opinión.
ToolmakerSteve
8

Puedes usar:

JsonConvert.PopulateObject(json, obj);

aquí: jsones la cadena json, objes el objeto de destino. Ver: ejemplo

Nota: PopulateObject()no borrará los datos de la lista de obj, después Populate(), el obj'smiembro de la lista contendrá sus datos originales y los datos de la cadena json

bbants
fuente
2
es PopulateObject: Populate no está en el modelo de objetos.
amok
1
¡Esto funcionó PERFECTO para mí! Estaba teniendo problemas en los que tenía una cadena JSON bastante compleja, cuando intenté convertirla en objetos C #, cualquier cosa marcada como 'NotNull' faltaría en el objeto, a pesar de que, para empezar, estaba presente en la cadena JSON. Muy extraño. ¡Utilicé este método y funcionó PERFECTO!
jward01
3

Partiendo de la respuesta de bbant, esta es mi solución completa para deserializar JSON desde una URL remota.

using Newtonsoft.Json;
using System.Net.Http;

namespace Base
{
    public class ApiConsumer<T>
    {
        public T data;
        private string url;

        public CalendarApiConsumer(string url)
        {
            this.url = url;
            this.data = getItems();
        }

        private T getItems()
        {
            T result = default(T);
            HttpClient client = new HttpClient();

            // This allows for debugging possible JSON issues
            var settings = new JsonSerializerSettings
            {
                Error = (sender, args) =>
                {
                    if (System.Diagnostics.Debugger.IsAttached)
                    {
                        System.Diagnostics.Debugger.Break();
                    }
                }
            };

            using (HttpResponseMessage response = client.GetAsync(this.url).Result)
            {
                if (response.IsSuccessStatusCode)
                {
                    result = JsonConvert.DeserializeObject<T>(response.Content.ReadAsStringAsync().Result, settings);
                }
            }
            return result;
        }
    }
}

El uso sería como:

ApiConsumer<FeedResult> feed = new ApiConsumer<FeedResult>("http://example.info/feeds/feeds.aspx?alt=json-in-script");

¿Dónde FeedResultse genera la clase con Xamasoft JSON Class Generator?

Aquí hay una captura de pantalla de la configuración que utilicé, lo que permite nombres de propiedades extraños que la versión web no podía explicar.

Generador de clases Xamasoft JSON

Kyle Falconer
fuente
1

Encontré que había construido mi objeto incorrectamente. Solía http://json2csharp.com/ a mí generar mi clase de objeto de la JSON. Una vez que obtuve el Oject correcto pude lanzar sin problemas. Norbit, error de Noob. Pensé en agregarlo en caso de que tenga el mismo problema.

Adán
fuente
1

Puede intentar consultar algunos de los generadores de clase en línea para obtener más información. Sin embargo, creo que algunas de las respuestas han sido útiles. Aquí está mi enfoque que puede ser útil.

El siguiente código fue hecho con un método dinámico en mente.

dynObj = (JArray) JsonConvert.DeserializeObject(nvm);

foreach(JObject item in dynObj) {
 foreach(JObject trend in item["trends"]) {
  Console.WriteLine("{0}-{1}-{2}", trend["query"], trend["name"], trend["url"]);
 }
}

Este código básicamente le permite acceder a los miembros contenidos en la cadena Json. Simplemente una forma diferente sin la necesidad de las clases. query, trendyurl son los objetos contenidos en la cadena Json.

También puedes usar este sitio web . No confíes en las clases al 100%, pero tienes la idea.

Edward Newgate
fuente
0

Suponiendo que sus datos de muestra sean correctos, su nombre de pila y otras entradas entre paréntesis son matrices en JS ... querrá usar Lista para esos tipos de datos. y Lista para decir accountstatusexpmaxdate ... Creo que su ejemplo tiene las fechas incorrectamente formateadas, por lo que no está seguro de qué más es incorrecto en su ejemplo.

Esta es una publicación antigua, pero quería tomar nota de los problemas.

Rastreador1
fuente