web-dev-qa-db-fra.com

Sérialisation d'un objet au format XML UTF-8 dans .NET

L'élimination appropriée des objets a été supprimée pour des raisons de brièveté, mais je suis choqué s'il s'agit du moyen le plus simple de coder un objet au format UTF-8 en mémoire. Il doit y avoir un moyen plus facile n'est-ce pas?

var serializer = new XmlSerializer(typeof(SomeSerializableObject));

var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream, System.Text.Encoding.UTF8);

serializer.Serialize(streamWriter, entry);

memoryStream.Seek(0, SeekOrigin.Begin);
var streamReader = new StreamReader(memoryStream, System.Text.Encoding.UTF8);
var utf8EncodedXml = streamReader.ReadToEnd();
101
Garry Shutler

Votre code ne récupère pas l'UTF-8 en mémoire lorsque vous le relisez dans une chaîne. Il n'est donc plus en UTF-8 mais en UTF-16 (idéalement, il est préférable de considérer les chaînes à un niveau supérieur à tout codage, sauf en cas de nécessité).

Pour obtenir les octets UTF-8 réels, vous pouvez utiliser:

var serializer = new XmlSerializer(typeof(SomeSerializableObject));

var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream, System.Text.Encoding.UTF8);

serializer.Serialize(streamWriter, entry);

byte[] utf8EncodedXml = memoryStream.ToArray();

J'ai laissé de côté la même disposition que vous avez laissée. Je suis légèrement en faveur des points suivants (avec une élimination normale laissée à l'intérieur):

var serializer = new XmlSerializer(typeof(SomeSerializableObject));
using(var memStm = new MemoryStream())
using(var  xw = XmlWriter.Create(memStm))
{
  serializer.Serialize(xw, entry);
  var utf8 = memStm.ToArray();
}

Ce qui correspond à peu près à la même complexité, mais montre qu’à chaque étape, il existe un choix raisonnable pour faire autre chose, dont le plus urgent est de sérialiser dans un endroit autre que la mémoire, comme un fichier, TCP/IP. flux, base de données, etc. Dans l'ensemble, ce n'est pas vraiment ce verbose.

52
Jon Hanna

Non, vous pouvez utiliser un StringWriter pour vous débarrasser de l'intermédiaire MemoryStream. Cependant, pour le forcer en XML, vous devez utiliser un StringWriter qui remplace la propriété Encoding:

public class Utf8StringWriter : StringWriter
{
    public override Encoding Encoding => Encoding.UTF8;
}

Ou si vous n'utilisez pas encore le C # 6:

public class Utf8StringWriter : StringWriter
{
    public override Encoding Encoding { get { return Encoding.UTF8; } }
}

Ensuite:

var serializer = new XmlSerializer(typeof(SomeSerializableObject));
string utf8;
using (StringWriter writer = new Utf8StringWriter())
{
    serializer.Serialize(writer, entry);
    utf8 = writer.ToString();
}

Évidemment, vous pouvez faire Utf8StringWriter dans une classe plus générale qui accepte n’importe quel encodage dans son constructeur - mais selon mon expérience, UTF-8 est de loin l’encodage "personnalisé" le plus communément requis pour un StringWriter :)

Maintenant, comme le dit Jon Hanna, ce sera toujours UTF-16 en interne, mais vous allez probablement le passer à autre chose à un moment donné, pour le convertir en données binaires ... à ça point vous pouvez utiliser la chaîne ci-dessus, la convertir en octets UTF-8, et tout ira bien - car la déclaration XML spécifiera "utf-8" comme codage.

EDIT: Un exemple court mais complet pour montrer que cela fonctionne:

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

public class Test
{    
    public int X { get; set; }

    static void Main()
    {
        Test t = new Test();
        var serializer = new XmlSerializer(typeof(Test));
        string utf8;
        using (StringWriter writer = new Utf8StringWriter())
        {
            serializer.Serialize(writer, t);
            utf8 = writer.ToString();
        }
        Console.WriteLine(utf8);
    }


    public class Utf8StringWriter : StringWriter
    {
        public override Encoding Encoding => Encoding.UTF8;
    }
}

Résultat:

<?xml version="1.0" encoding="utf-8"?>
<Test xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <X>0</X>
</Test>

Notez le codage déclaré de "utf-8" qui est ce que nous voulions, je crois.

256
Jon Skeet

Très bonne réponse en utilisant l'héritage, n'oubliez pas de remplacer l'initialiseur

public class Utf8StringWriter : StringWriter
{
    public Utf8StringWriter(StringBuilder sb) : base (sb)
    {
    }
    public override Encoding Encoding { get { return Encoding.UTF8; } }
}
17
Sebastian Castaldi

J'ai trouvé ce billet de blog qui explique très bien le problème et définit différentes solutions:

(lien mort enlevé)

J'ai opté pour l'idée que la meilleure façon de le faire est d'omettre complètement la déclaration XML en mémoire. En fait is UTF-16 à ce stade de toute façon, mais la déclaration XML ne semble pas avoir de sens tant qu’elle n’a pas été écrite dans un fichier avec un codage particulier; et même dans ce cas, la déclaration n'est pas requise. Cela ne semble pas briser la désérialisation, du moins.

Comme @Jon Hanna le mentionne, ceci peut être fait avec un XmlWriter créé comme ceci:

XmlWriter writer = XmlWriter.Create (output, new XmlWriterSettings() { OmitXmlDeclaration = true });
5
Dave Andersen