.NETでオブジェクトをUTF-8 XMLとしてシリアル化する


112

適切なオブジェクトの破棄は簡潔にするために削除されましたが、これがオブジェクトをメモリ内でUTF-8としてエンコードする最も簡単な方法であるとは驚きです。より簡単な方法がなければなりませんか?

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


1
私は混乱しています...デフォルトのエンコーディングUTF-8ではありませんか?
flq

@flq、はい、デフォルトはUTF-8ですが、彼がそれを再び文字列に読み直しているためutf8EncodedXml、UTF-16はそれほど重要ではありません。
Jon Hanna

1
ジョン・スキートと私は別の質問に答えているので、@ギャリー、はっきりさせてもらえますか?オブジェクトをUTF-8としてシリアル化しますか、それともUTF-8として宣言するXML文字列が必要ですか?したがって、後でUTF-8でエンコードしたときに正しい宣言が行われますか?(その場合、UTF-8とUTF-16の両方に有効であるため、最も簡単な方法は宣言がないことです)。
Jon Hanna

@ジョン読み返して、私の質問にはあいまいさがあります。主にデバッグ目的で文字列に出力しました。実際には、ディスクまたはHTTPを介してバイトをストリーミングしているので、私の問題により直接的に関連する回答になります。私が抱えていた主な問題は、XMLでのUTF-8の宣言でしたが、より正確に言えば、プラットフォームに依存するのではなく、実際に送信/永続化するUTF-8バイトを行うように、文字列の中間を回避する必要があります(私は思う)エンコーディング。
Garry Shutler、

回答:


55

コードは、再度文字列に読み込むときにUTF-8をメモリに格納しないため、UTF-8ではなく、UTF-16に戻ります(理想的には、より高いレベルの文字列を検討するのが最善ですが)強制する場合を除いて、任意のエンコーディング)。

実際のUTF-8オクテットを取得するには、次を使用できます。

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

私はあなたが残したのと同じ処分を省略しました。私は次のことをわずかに好みます(通常の処分は残します):

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

これはほぼ同じ量の複雑さですが、すべての段階で何か他のことを実行するための合理的な選択があることを示しています。最も差し迫っているのは、ファイルやTCP / IPなどのメモリ以外の場所にシリアル化することです。ストリーム、データベースなど。全体としては、それほど冗長ではありません。


4
また。BOMを抑制したい場合は、を使用できますXmlWriter.Create(memoryStream, new XmlWriterSettings { Encoding = new UTF8Encoding(false) })
オニー

Jonのショーのように作成されたXMLを誰かが読む必要がある場合は、メモリストリームを0に再配置することを忘れないでください。そうしないと、「ルート要素がありません」という例外が発生します。これを行います:memStm.Position = 0; XmlReader xmlReader = XmlReader.Create(memStm)
Sudhanshu Mishra

276

いいえ、StringWriter中間体を取り除くためにa を使用できますMemoryStream。ただし、強制的にXMLにするにはStringWriterEncodingプロパティをオーバーライドするを使用する必要があります。

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

または、まだC#6を使用していない場合:

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

次に:

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

明らかUtf8StringWriterに、コンストラクターで任意のエンコードを受け入れるより一般的なクラスにすることができますが、私の経験では、UTF-8は、StringWriter:)で最も一般的に必要な「カスタム」エンコードです。

これでJon Hannaが言うように、これは内部的にはまだUTF-16ですが、おそらく別の場所にそれを渡してバイナリデータに変換します... その時点で上記の文字列を使用できます。それをUTF-8バイトに変換します。XML宣言では "utf-8"がエンコードとして指定されるため、すべて順調です。

編集:これが機能していることを示す短いが完全な例:

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

結果:

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

「utf-8」の宣言されたエンコーディングに注意してください。これは、私たちが望んでいたものだと思います。


2
StringWriterのEncodingパラメータをオーバーライドした場合でも、書き込まれたデータは引き続きStringBuilderに送信されるため、依然としてUTF-16です。また、文字列はUTF-16にしかできません。
Jon Hanna

3
@ジョン:あなたはそれを試しましたか?私は持っています、そしてそれはうまくいきます。ここで重要なのは宣言されたエンコーディングです。明らかに内部的には文字列はまだUTF-16ですが、バイナリに変換されるまで違いはありません(UTF-8を含む任意のエンコーディングを使用できます)。TextWriter.Encodingプロパティは、文書自体の中で指定したエンコーディング名を決定するためにXMLシリアライザで使用されます。
Jon Skeet、

2
@ジョン:そして宣言されたエンコーディングは何でしたか?私の経験では、これがこのような質問が実際にやろうとしていることです。UTF-8であると宣言するXMLドキュメントを作成してください。あなたが言うように、それはにするために、テキストを考慮するのが最善ではありません任意のあなたに...しかし、XML文書のように必要になるまでのエンコードを宣言の何かが、あなたが考慮する必要があることを、エンコーディングを。
Jon Skeet、

2
@Garry、私は今の考えることができる最も簡単なのは私の答えで第二の例を取ることですが、あなたが作成したときにXmlWriterとるファクトリメソッドでそうするXmlWriterSettingsオブジェクトを、と持っているOmitXmlDeclarationのプロパティセットをtrue
Jon Hanna

4
+1あなたのUtf8StringWriter解決策は非常に素晴らしく、きれいです
Adriano Carneiro 2012

17

継承を使用した非常に良い答え、初期化子をオーバーライドすることを忘れないでください

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

おかげで、私はこれが最もエレガントなオプションであると思います
Prokurors

5

私は問題を非常によく説明し、いくつかの異なる解決策を定義するこのブログ投稿を見つけました:

(デッドリンクは削除されました)

私は、それを行うための最良の方法は、メモリ内でXML宣言を完全に省略することであるという考えに落ち着きました。いずれにせよ実際にその時点で UTF-16ですが、XML宣言は、特定のエンコーディングでファイルに書き込まれるまで意味がありません。それでも、宣言は必要ありません。少なくとも、逆シリアル化を壊すことはないようです。

@Jon Hannaが言及しているように、これは次のように作成されたXmlWriterで実行できます。

XmlWriter writer = XmlWriter.Create (output, new XmlWriterSettings() { OmitXmlDeclaration = true });
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.