Desserializando dados JSON para C # usando JSON.NET

144

Sou relativamente novo em trabalhar com dados C # e JSON e estou buscando orientação. Estou usando o C # 3.0, com .NET3.5SP1 e JSON.NET 3.5r6.

Eu tenho uma classe C # definida que preciso preencher a partir de uma estrutura JSON. No entanto, nem toda estrutura JSON para uma entrada que é recuperada do serviço da web contém todos os atributos possíveis definidos na classe C #.

Eu venho fazendo o que parece ser errado e difícil e apenas escolhendo cada valor um por um no JObject e transformando a string na propriedade de classe desejada.

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

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

Qual é a melhor maneira de desserializar uma estrutura JSON para a classe C # e manipular possíveis dados ausentes da origem JSON?

Minha classe é definida 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; }

  }

E uma amostra do JSON para analisar é:

{
    "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
fonte

Respostas:

76

Você já tentou usar o método DeserializeObject genérico?

JsonConvert.DeserializeObject<MyAccount>(myjsondata);

Quaisquer campos ausentes nos dados JSON devem simplesmente ser deixados NULL.

ATUALIZAR:

Se a cadeia JSON for uma matriz, tente o seguinte:

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

jarraydeve então ser a List<MyAccount>.

OUTRA ATUALIZAÇÃO:

A exceção que você está recebendo não é consistente com uma matriz de objetos - acho que o serializador está tendo problemas com sua accountstatusmodifiedbypropriedade do tipo Dicionário .

Tente excluir a accountstatusmodifiedby propriedade da serialização e veja se isso ajuda. Nesse caso, pode ser necessário representar essa propriedade de maneira diferente.

Documentation: JSON serializando e desserializando com o Json.NET

Dave Swersky
fonte
Obrigado. No entanto, recebo um erro de "Não é possível desserializar a matriz JSON para o tipo 'System.String'." quando está tentando desserializar (por exemplo) a matriz JSON namedname na string de classe GivenName. Os atributos JSON que defini como sequência na classe C # são apenas matrizes de elemento único. É por isso que comecei a escolher os valores um por um, ao me deparar com esse tipo de problema durante o processo de desserialização. Outra magia que estou ignorando?
Então ... DateTime AccountStatusExpiration(por exemplo) não é anulável, conforme definido no código. O que seria necessário para torná-lo anulável? Simplesmente mude DateTimepara DateTime??
Hamish Grubijan
50

Resposta reproduzida de https://stackoverflow.com/a/10718128/776476

Você pode usar o dynamictipo C # para facilitar as coisas. Essa técnica também simplifica a re-fatoração, pois não depende de seqüências mágicas.

Json

A jsonsequência abaixo é uma resposta simples de uma chamada http api e define duas propriedades: Ide Name.

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

C #

Use JsonConvert.DeserializeObject<dynamic>()para desserializar essa sequência em um tipo dinâmico e simplesmente acessar suas propriedades da maneira usual.

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

Nota : O link NuGet para a montagem NewtonSoft é http://nuget.org/packages/newtonsoft.json . Não se esqueça de adicionar: using Newtonsoft.Json;para acessar essas classes.

biofractal
fonte
7
Adoro o uso de <dynamic>. No entanto, eu tinha que fazer isso para fazê-lo funcionar: resultado [ "Id"] Valor e resultado [ "nome"] Valor..
fredw
1
Embora dinâmico seja uma boa alternativa, os exemplos acima não estão usando 'strings mágicas', mas sim genéricos fortemente tipados que são atualizados durante técnicas normais de refatoração de VS (a menos que dentro de uma View no MVC que esteja fora do escopo desta pergunta).
Gcoleman0828
4
Você ainda tem 'cordas mágicas', agora elas são apenas ocultas pelo uso da dinâmica!
Ian Ringrose
De fato, o biofractal o retrocede em relação a "cordas mágicas", como aponta o @ IanRingrose. O objeto dinâmico retornado por DeserializeObject<dynamic>é, por definição, não "verificado por tipo". Portanto, as seguintes linhas results.Ide results.Namenão podem ser verificadas em tempo de compilação. Quaisquer erros ocorrerão no tempo de execução. Compare isso com uma chamada fortemente digitada, como DeserializeObject<List<MyAccount>>. Referências a essas propriedades podem ser confirmadas em tempo de compilação. O uso de dynamic, embora possa ser conveniente, introduz "cordas mágicas" como nomes de propriedades; uma redução na robustez IMHO.
Página Inicial>
8

Você pode usar:

JsonConvert.PopulateObject(json, obj);

aqui: jsoné a string json, objé o objeto de destino. Veja: exemplo

Nota: PopulateObject()não apagará os dados da lista de objetos; depois Populate(), o obj'smembro da lista conterá seus dados e dados originais da string json

bbants
fonte
2
é PopulateObject - O preenchimento não está no modelo de objeto.
31512 amok
1
Isso funcionou PERFEITO para mim! Eu estava tendo problemas em que tinha uma sequência JSON bastante complexa, quando tentei convertê-la em objetos C #, qualquer coisa marcada como 'NotNull' estaria faltando no objeto, mesmo que estivesse presente na sequência JSON. Muito estranho. Eu usei esse método e funcionou PERFEITO!
jward01
3

Com base na resposta da bbant, esta é minha solução completa para desserializar o JSON a partir de uma 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;
        }
    }
}

O uso seria como:

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

Onde FeedResulté a classe gerada usando o Xamasoft JSON Class Generator

Aqui está uma captura de tela das configurações que eu usei, permitindo nomes de propriedades estranhos que a versão da Web não podia ser responsável.

Gerador de classe Xamasoft JSON

Kyle Falconer
fonte
1

Descobri que havia construído meu objeto incorretamente. Usei http://json2csharp.com/ para gerar minha classe de objeto a partir do JSON. Depois de ter o Oject correto, pude lançar sem problemas. Norbit, erro de Noob. Pensei em adicioná-lo caso você tenha o mesmo problema.

Adão
fonte
1

Você pode tentar verificar alguns dos geradores de classe on-line para obter mais informações. No entanto, acredito que algumas das respostas foram úteis. Aqui está a minha abordagem que pode ser útil.

O código a seguir foi criado com um método dinâmico em 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"]);
 }
}

Esse código basicamente permite acessar membros contidos na string Json. Apenas uma maneira diferente, sem a necessidade das aulas. query, trendE urlsão os objetos contidos na cadeia de caracteres JSON.

Você também pode usar este site . Não confie nas aulas 100%, mas você entendeu.

Edward Newgate
fonte
0

Supondo que seus dados de amostra estejam corretos, seu nome e outras entradas entre colchetes são matrizes em JS ... você desejará usar a Lista para esses tipos de dados. e List for say accountstatusexpmaxdate ... Acho que o exemplo tem as datas formatadas incorretamente, porém, tão incertas quanto ao que mais está incorreto no seu exemplo.

Este é um post antigo, mas queria anotar os problemas.

Tracker1
fonte