Como desserializar documento XML

472

Como desserializar este documento XML:

<?xml version="1.0" encoding="utf-8"?>
<Cars>
  <Car>
    <StockNumber>1020</StockNumber>
    <Make>Nissan</Make>
    <Model>Sentra</Model>
  </Car>
  <Car>
    <StockNumber>1010</StockNumber>
    <Make>Toyota</Make>
    <Model>Corolla</Model>
  </Car>
  <Car>
    <StockNumber>1111</StockNumber>
    <Make>Honda</Make>
    <Model>Accord</Model>
  </Car>
</Cars>

Eu tenho isto:

[Serializable()]
public class Car
{
    [System.Xml.Serialization.XmlElementAttribute("StockNumber")]
    public string StockNumber{ get; set; }

    [System.Xml.Serialization.XmlElementAttribute("Make")]
    public string Make{ get; set; }

    [System.Xml.Serialization.XmlElementAttribute("Model")]
    public string Model{ get; set; }
}

.

[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
    [XmlArrayItem(typeof(Car))]
    public Car[] Car { get; set; }

}

.

public class CarSerializer
{
    public Cars Deserialize()
    {
        Cars[] cars = null;
        string path = HttpContext.Current.ApplicationInstance.Server.MapPath("~/App_Data/") + "cars.xml";

        XmlSerializer serializer = new XmlSerializer(typeof(Cars[]));

        StreamReader reader = new StreamReader(path);
        reader.ReadToEnd();
        cars = (Cars[])serializer.Deserialize(reader);
        reader.Close();

        return cars;
    }
}

que não parecem funcionar :-(

Alex
fonte
Eu acho que você precisa escapar dos colchetes angulares no seu documento de amostra.
harpo 12/12/08
4
Esta resposta é realmente muito boa: stackoverflow.com/a/19613934/196210
Revious

Respostas:

359

Aqui está uma versão de trabalho. Alterei os XmlElementAttributerótulos para, XmlElementporque no xml os valores StockNumber, Make e Model são elementos, não atributos. Também removi o reader.ReadToEnd();(essa função lê todo o fluxo e retorna uma string, para que a Deserialize()função não pudesse mais usar o leitor ... a posição estava no final do fluxo). Eu também tomei algumas liberdades com a nomeação :).

Aqui estão as aulas:

[Serializable()]
public class Car
{
    [System.Xml.Serialization.XmlElement("StockNumber")]
    public string StockNumber { get; set; }

    [System.Xml.Serialization.XmlElement("Make")]
    public string Make { get; set; }

    [System.Xml.Serialization.XmlElement("Model")]
    public string Model { get; set; }
}


[Serializable()]
[System.Xml.Serialization.XmlRoot("CarCollection")]
public class CarCollection
{
    [XmlArray("Cars")]
    [XmlArrayItem("Car", typeof(Car))]
    public Car[] Car { get; set; }
}

A função desserializar:

CarCollection cars = null;
string path = "cars.xml";

XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));

StreamReader reader = new StreamReader(path);
cars = (CarCollection)serializer.Deserialize(reader);
reader.Close();

E o xml ligeiramente aprimorado (eu precisava adicionar um novo elemento para agrupar <Cars> ... Net é exigente quanto à desserialização de matrizes):

<?xml version="1.0" encoding="utf-8"?>
<CarCollection>
<Cars>
  <Car>
    <StockNumber>1020</StockNumber>
    <Make>Nissan</Make>
    <Model>Sentra</Model>
  </Car>
  <Car>
    <StockNumber>1010</StockNumber>
    <Make>Toyota</Make>
    <Model>Corolla</Model>
  </Car>
  <Car>
    <StockNumber>1111</StockNumber>
    <Make>Honda</Make>
    <Model>Accord</Model>
  </Car>
</Cars>
</CarCollection>
Kevin Tighe
fonte
68
O [Serializable]é redundante se estiver usando XmlSerializer; XmlSerializersimplesmente nunca verifica isso. Da mesma forma, a maioria dos [Xml...]atributos é redundante, pois simplesmente imita o comportamento padrão; ou seja, por padrão, uma propriedade chamada StockNumberé armazenada como um elemento chamado <StockNumber>- não há necessidade de atributos para isso.
Marc Gravell
3
Observe que XmlElementAttribute = XmlElement (é um recurso de idioma que você pode omitir o sufixo "Attribute"). A solução real aqui é a remoção da chamada ReadToEnd () e a adição de um nó raiz. Mas é melhor usar o código de erymski que resolve a questão (analisar o XML dada)
Flamefire
2
Obrigado Kevin, mas e se eu removesse o CarsCollection do XML de amostra. Eu removi o Carscollection das classes e deserealize o código, mas não obtive sucesso.
21915 Vikrant
441

Que tal salvar o xml em um arquivo e usar o xsd para gerar classes C #?

  1. Escreva o arquivo no disco (eu o chamei foo.xml)
  2. Gere o xsd: xsd foo.xml
  3. Gere o c #: xsd foo.xsd /classes

Et voila - e arquivo de código C # que deve poder ler os dados via XmlSerializer:

    XmlSerializer ser = new XmlSerializer(typeof(Cars));
    Cars cars;
    using (XmlReader reader = XmlReader.Create(path))
    {
        cars = (Cars) ser.Deserialize(reader);
    }

(inclua os foo.cs gerados no projeto)

Marc Gravell
fonte
6
Você é o cara! Obrigado. para qualquer pessoa que precise, "path" pode ser um fluxo criado a partir de uma resposta da Web da seguinte maneira: var resp = response.Content.ReadAsByteArrayAsync (); var stream = novo MemoryStream (resp.Result);
Induster
1
Ótima idéia, mas não consegui que funcionasse corretamente para o meu modelo um pouco mais complicado com lotes de matrizes aninhadas. Eu continuava recebendo erros de conversão de tipo para as matrizes aninhadas - além do esquema de nomenclatura gerado, deixava algo a desejar. Portanto, acabei seguindo a rota personalizada.
9788 Goteborn # 1:
9
Como chegar a Xsd.exe
jwillmer
2
xsd.exe está disponível no prompt de comando do visual studio, não no prompt de comando do Windows. Veja se você pode abrir o prompt de comando no Visual Studio em Ferramentas. Caso contrário, tente acessá-lo na pasta visual studio. Para o VS 2012, ele estava localizado aqui: C: \ Arquivos de Programas (x86) \ Microsoft Visual Studio 12.0 \ Common7 \ Tools \ Shortcuts. No Windows 8, tente pesquisar por "Visual Studio Tools".
Goku_da_master 16/10
2
Para todos que procuram XSD. Aqui está um thread SO: stackoverflow.com/questions/22975031/…
SOReader
229

Você tem duas possibilidades.

Método 1. Ferramenta XSD


Suponha que você tenha seu arquivo XML neste local C:\path\to\xml\file.xml

  1. Abra o Prompt de Comando do Desenvolvedor
    Você pode encontrá-lo em Start Menu > Programs > Microsoft Visual Studio 2012 > Visual Studio Tools Ou, se você possui o Windows 8, pode começar a digitar Prompt de Comando do Desenvolvedor na tela Iniciar
  2. Altere o local para o diretório de arquivos XML digitando cd /D "C:\path\to\xml"
  3. Crie um arquivo XSD a partir do seu arquivo xml digitandoxsd file.xml
  4. Crie classes C # digitandoxsd /c file.xsd

E é isso! Você gerou classes C # do arquivo xml emC:\path\to\xml\file.cs

Método 2 - Colar especial


Visual Studio 2012+ necessário

  1. Copie o conteúdo do seu arquivo XML para a área de transferência
  2. Adicione à sua solução novo arquivo de classe vazio ( Shift+ Alt+ C)
  3. Abra esse arquivo e, no menu, clique em Edit > Paste special > Paste XML As Classes
    insira a descrição da imagem aqui

E é isso!

Uso


O uso é muito simples com esta classe auxiliar:

using System;
using System.IO;
using System.Web.Script.Serialization; // Add reference: System.Web.Extensions
using System.Xml;
using System.Xml.Serialization;

namespace Helpers
{
    internal static class ParseHelpers
    {
        private static JavaScriptSerializer json;
        private static JavaScriptSerializer JSON { get { return json ?? (json = new JavaScriptSerializer()); } }

        public static Stream ToStream(this string @this)
        {
            var stream = new MemoryStream();
            var writer = new StreamWriter(stream);
            writer.Write(@this);
            writer.Flush();
            stream.Position = 0;
            return stream;
        }


        public static T ParseXML<T>(this string @this) where T : class
        {
            var reader = XmlReader.Create(@this.Trim().ToStream(), new XmlReaderSettings() { ConformanceLevel = ConformanceLevel.Document });
            return new XmlSerializer(typeof(T)).Deserialize(reader) as T;
        }

        public static T ParseJSON<T>(this string @this) where T : class
        {
            return JSON.Deserialize<T>(@this.Trim());
        }
    }
}

Tudo o que você precisa fazer agora é:

    public class JSONRoot
    {
        public catalog catalog { get; set; }
    }
    // ...

    string xml = File.ReadAllText(@"D:\file.xml");
    var catalog1 = xml.ParseXML<catalog>();

    string json = File.ReadAllText(@"D:\file.json");
    var catalog2 = json.ParseJSON<JSONRoot>();
Damian Drygiel
fonte
16
+1 boa resposta. Mas, os Paste XML As Classesalvos de comando única .NET 4.5
Ravy Amiry
1
Essa é uma ótima maneira de gerar o modelo se você tiver o vs2012 + instalado. Executei a limpeza do código ReSharper posteriormente para usar propriedades automáticas e, em seguida, fiz outras arrumações. Você pode gerar por esse método e, em seguida, copiar em um projeto mais antigo, se necessário.
Scotty.NET
4
A segmentação de .net4.5 não é um problema. Apenas inicie um projeto temporário com o dotnet4.5, faça sua cópia e cole lá e copie a fonte para o seu projeto real.
LosManos 23/03
2
onde está o objeto ou a classe "catálogo"?
CB4
3
Para "Colar XML como classes" aparecer nesse menu na Comunidade VS 2017, você precisa ter instalado "ASP.NET and web development". Se estiver ausente, execute o instalador do VS novamente para modificar sua instalação.
Slion
89

O seguinte snippet deve fazer o truque (e você pode ignorar a maioria dos atributos de serialização):

public class Car
{
  public string StockNumber { get; set; }
  public string Make { get; set; }
  public string Model { get; set; }
}

[XmlRootAttribute("Cars")]
public class CarCollection
{
  [XmlElement("Car")]
  public Car[] Cars { get; set; }
}

...

using (TextReader reader = new StreamReader(path))
{
  XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
  return (CarCollection) serializer.Deserialize(reader);
}

fonte
14
Esta é realmente a única resposta. A resposta aceita tem algumas falhas que podem confundir iniciantes.
Flamefire 24/10
24

Veja se isso ajuda:

[Serializable()]
[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
    [XmlArrayItem(typeof(Car))]
    public Car[] Car { get; set; }
}

.

[Serializable()]
public class Car
{
    [System.Xml.Serialization.XmlElement()]
    public string StockNumber{ get; set; }

    [System.Xml.Serialization.XmlElement()]
    public string Make{ get; set; }

    [System.Xml.Serialization.XmlElement()]
    public string Model{ get; set; }
}

Caso contrário, use o programa xsd.exe que acompanha o visual studio para criar um documento de esquema com base nesse arquivo xml e, em seguida, use-o novamente para criar uma classe com base no documento de esquema.

Joel Coehoorn
fonte
9

Não acho que .net seja "exigente quanto à desserialização de matrizes". O primeiro documento xml não está bem formado. Não há elemento raiz, embora pareça que exista. O documento xml canônico possui uma raiz e pelo menos 1 elemento (se houver). No seu exemplo:

<Root> <-- well, the root
  <Cars> <-- an element (not a root), it being an array
    <Car> <-- an element, it being an array item
    ...
    </Car>
  </Cars>
</Root>
janbak
fonte
7

tente este bloco de código se o seu arquivo .xml foi gerado em algum lugar do disco e se você usou List<T>:

//deserialization

XmlSerializer xmlser = new XmlSerializer(typeof(List<Item>));
StreamReader srdr = new StreamReader(@"C:\serialize.xml");
List<Item> p = (List<Item>)xmlser.Deserialize(srdr);
srdr.Close();`

Nota: C:\serialize.xmlé o caminho do meu arquivo .xml. Você pode alterá-lo para suas necessidades.

nainwal de folha
fonte
6

A ansiedade de Kevin é boa, além do fato de que, no mundo real, muitas vezes você não pode alterar o XML original para atender às suas necessidades.

Também existe uma solução simples para o XML original:

[XmlRoot("Cars")]
public class XmlData
{
    [XmlElement("Car")]
    public List<Car> Cars{ get; set; }
}

public class Car
{
    public string StockNumber { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }
}

E então você pode simplesmente ligar para:

var ser = new XmlSerializer(typeof(XmlData));
XmlData data = (XmlData)ser.Deserialize(XmlReader.Create(PathToCarsXml));
Kim Homann
fonte
Obrigado! Sua resposta é exatamente o que eu precisava, pois não queria alterar o valor de gigabytes de arquivos de log.
Colin
Embora valha a pena mencionar que a solução XmlSerializer é muito elegante, mas também não muito rápida e reage com sensibilidade a dados Xml inesperados. Portanto, se seu problema não exigir uma desserialização completa, considere usar apenas a classe XmlReader mais pragmática e com melhor desempenho e percorrer os elementos <Car>.
Kim Homann
5

Experimente esta classe genérica para serialização e desserialização de XML.

public class SerializeConfig<T> where T : class
{
    public static void Serialize(string path, T type)
    {
        var serializer = new XmlSerializer(type.GetType());
        using (var writer = new FileStream(path, FileMode.Create))
        {
            serializer.Serialize(writer, type);
        }
    }

    public static T DeSerialize(string path)
    {
        T type;
        var serializer = new XmlSerializer(typeof(T));
        using (var reader = XmlReader.Create(path))
        {
            type = serializer.Deserialize(reader) as T;
        }
        return type;
    }
}
Hasan Javaid
fonte
4

Para iniciantes

Achei as respostas aqui muito úteis, que diziam que ainda lutava (apenas um pouco) para fazer isso funcionar. Portanto, caso ajude alguém, explicarei a solução de trabalho:

XML da pergunta original. O xml está em um arquivo Class1.xml, um pathpara esse arquivo é usado no código para localizar esse arquivo xml.

Eu usei a resposta de @erymski para fazer isso funcionar, então criei um arquivo chamado Car.cs e adicionei o seguinte:

using System.Xml.Serialization;  // Added

public class Car
{
    public string StockNumber { get; set; }
    public string Make { get; set; }
    public string Model { get; set; }
}

[XmlRootAttribute("Cars")]
public class CarCollection
{
    [XmlElement("Car")]
    public Car[] Cars { get; set; }
}

O outro código fornecido por @erymski ...

using (TextReader reader = new StreamReader(path))
{
  XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
  return (CarCollection) serializer.Deserialize(reader);
}

... entra no seu programa principal (Program.cs), static CarCollection XCar()assim:

using System;
using System.IO;
using System.Xml.Serialization;

namespace ConsoleApp2
{
    class Program
    {

        public static void Main()
        {
            var c = new CarCollection();

            c = XCar();

            foreach (var k in c.Cars)
            {
                Console.WriteLine(k.Make + " " + k.Model + " " + k.StockNumber);
            }
            c = null;
            Console.ReadLine();

        }
        static CarCollection XCar()
        {
            using (TextReader reader = new StreamReader(@"C:\Users\SlowLearner\source\repos\ConsoleApp2\ConsoleApp2\Class1.xml"))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(CarCollection));
                return (CarCollection)serializer.Deserialize(reader);
            }
        }
    }
}

Espero que ajude :-)

Aprendiz lento
fonte
1
Funcionou para mim. Esta é uma solução perfeitamente funcional para a entrada xml fornecida (como no exemplo do OP) também. [XmlElement ("Car")] é o atributo certo. Em outros exemplos, eles usaram o XmlArray etc, que não são necessários, desde que tenhamos a propriedade definida como public Car [] Cars {get; conjunto; } e a desserializaria corretamente. Obrigado.
Diwakar Padmaraja
3

Que tal uma classe genérica para desserializar um documento XML

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Generic class to load any xml into a class
// used like this ...
// YourClassTypeHere InfoList = LoadXMLFileIntoClass<YourClassTypeHere>(xmlFile);

using System.IO;
using System.Xml.Serialization;

public static T LoadXMLFileIntoClass<T>(string xmlFile)
{
    T returnThis;
    XmlSerializer serializer = new XmlSerializer(typeof(T));
    if (!FileAndIO.FileExists(xmlFile))
    {
        Console.WriteLine("FileDoesNotExistError {0}", xmlFile);
    }
    returnThis = (T)serializer.Deserialize(new StreamReader(xmlFile));
    return (T)returnThis;
}

Esta parte pode ou não ser necessária. Abra o documento XML no Visual Studio, clique com o botão direito do mouse no XML, escolha propriedades. Em seguida, escolha seu arquivo de esquema.

David C Fuchs
fonte
1
Isso me permitiu diminuir bastante o código da lógica de negócios e centralizar a funcionalidade em uma classe auxiliar com todas as classes <T> que eu gerei. Eu já tinha o XML em uma string, então poderia condensá-lo para isso: `public static T LoadXMLFileIntoClass <T> (string xmlData)` {`XmlSerializer serializer = new XmlSerializer (typeof (T)); `return (T) serializer.Deserialize (new StringReader (xmlData)); `} Obrigado!
pwrgreg007
3

Um forro:

var object = (Cars)new XmlSerializer(typeof(Cars)).Deserialize(new StringReader(xmlString));
Andre M
fonte
2

A idéia é ter todos os níveis sendo tratados para desserialização. Consulte uma solução de exemplo que resolveu meu problema semelhante

<?xml version="1.0" ?> 
 <TRANSACTION_RESPONSE>
    <TRANSACTION>
        <TRANSACTION_ID>25429</TRANSACTION_ID> 
        <MERCHANT_ACC_NO>02700701354375000964</MERCHANT_ACC_NO> 
        <TXN_STATUS>F</TXN_STATUS> 
        <TXN_SIGNATURE>a16af68d4c3e2280e44bd7c2c23f2af6cb1f0e5a28c266ea741608e72b1a5e4224da5b975909cc43c53b6c0f7f1bbf0820269caa3e350dd1812484edc499b279</TXN_SIGNATURE> 
        <TXN_SIGNATURE2>B1684258EA112C8B5BA51F73CDA9864D1BB98E04F5A78B67A3E539BEF96CCF4D16CFF6B9E04818B50E855E0783BB075309D112CA596BDC49F9738C4BF3AA1FB4</TXN_SIGNATURE2> 
        <TRAN_DATE>29-09-2015 07:36:59</TRAN_DATE> 
        <MERCHANT_TRANID>150929093703RUDZMX4</MERCHANT_TRANID> 
        <RESPONSE_CODE>9967</RESPONSE_CODE> 
        <RESPONSE_DESC>Bank rejected transaction!</RESPONSE_DESC> 
        <CUSTOMER_ID>RUDZMX</CUSTOMER_ID> 
        <AUTH_ID /> 
        <AUTH_DATE /> 
        <CAPTURE_DATE /> 
        <SALES_DATE /> 
        <VOID_REV_DATE /> 
        <REFUND_DATE /> 
        <REFUND_AMOUNT>0.00</REFUND_AMOUNT> 
    </TRANSACTION>
  </TRANSACTION_RESPONSE> 

O XML acima é tratado em dois níveis

  [XmlType("TRANSACTION_RESPONSE")]
public class TransactionResponse
{
    [XmlElement("TRANSACTION")]
    public BankQueryResponse Response { get; set; }

}

O nível interno

public class BankQueryResponse
{
    [XmlElement("TRANSACTION_ID")]
    public string TransactionId { get; set; }

    [XmlElement("MERCHANT_ACC_NO")]
    public string MerchantAccNo { get; set; }

    [XmlElement("TXN_SIGNATURE")]
    public string TxnSignature { get; set; }

    [XmlElement("TRAN_DATE")]
    public DateTime TranDate { get; set; }

    [XmlElement("TXN_STATUS")]
    public string TxnStatus { get; set; }


    [XmlElement("REFUND_DATE")]
    public DateTime RefundDate { get; set; }

    [XmlElement("RESPONSE_CODE")]
    public string ResponseCode { get; set; }


    [XmlElement("RESPONSE_DESC")]
    public string ResponseDesc { get; set; }

    [XmlAttribute("MERCHANT_TRANID")]
    public string MerchantTranId { get; set; }

}

Da mesma maneira que você precisa de vários níveis com car as array Verifique este exemplo para desserialização multinível

Makdu
fonte
1

Se você estiver recebendo erros ao usar o xsd.exe para criar seu arquivo xsd, use a classe XmlSchemaInference, conforme mencionado no msdn . Aqui está um teste de unidade para demonstrar:

using System.Xml;
using System.Xml.Schema;

[TestMethod]
public void GenerateXsdFromXmlTest()
{
    string folder = @"C:\mydir\mydata\xmlToCSharp";
    XmlReader reader = XmlReader.Create(folder + "\some_xml.xml");
    XmlSchemaSet schemaSet = new XmlSchemaSet();
    XmlSchemaInference schema = new XmlSchemaInference();

    schemaSet = schema.InferSchema(reader);


    foreach (XmlSchema s in schemaSet.Schemas())
    {
        XmlWriter xsdFile = new XmlTextWriter(folder + "\some_xsd.xsd", System.Text.Encoding.UTF8);
        s.Write(xsdFile);
        xsdFile.Close();
    }
}

// now from the visual studio command line type: xsd some_xsd.xsd /classes
goku_da_master
fonte
1

Você pode alterar apenas um atributo para sua propriedade de carro Cars, de XmlArrayItem para XmlElment. Ou seja, de

[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
    [XmlArrayItem(typeof(Car))]
    public Car[] Car { get; set; }
}

para

[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
    [XmlElement("Car")]
    public Car[] Car { get; set; }
}
XU Weijiang
fonte
1

Minha solução:

  1. Use Edit > Past Special > Paste XML As Classespara obter a classe no seu código
  2. Tente algo assim: crie uma lista dessa classe ( List<class1>) e use a XmlSerializerpara serializar essa lista em um xmlarquivo.
  3. Agora você acabou de substituir o corpo desse arquivo pelos seus dados e tentar deserializefazê - lo.

Código:

StreamReader sr = new StreamReader(@"C:\Users\duongngh\Desktop\Newfolder\abc.txt");
XmlSerializer xml = new XmlSerializer(typeof(Class1[]));
var a = xml.Deserialize(sr);
sr.Close();

NOTA: você deve prestar atenção ao nome raiz, não o altere. O meu é "ArrayOfClass1"

haiduong87
fonte
1
async public static Task<JObject> XMLtoNETAsync(XmlDocument ToConvert)
{
    //Van XML naar JSON
    string jsonText = await Task.Run(() => JsonConvert.SerializeXmlNode(ToConvert));

    //Van JSON naar .net object
    var o = await Task.Run(() => JObject.Parse(jsonText));

    return o;
}
Zoidbergseasharp
fonte
1
Coloque sua resposta sempre em contexto, em vez de apenas colar o código. Veja aqui para mais detalhes.
gehbiszumeis 24/05