Sérialiser un objet en chaîne

311

J'ai la méthode suivante pour enregistrer un objet dans un fichier:

// Save an object out to the disk
public static void SerializeObject<T>(this T toSerialize, String filename)
{
    XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());
    TextWriter textWriter = new StreamWriter(filename);

    xmlSerializer.Serialize(textWriter, toSerialize);
    textWriter.Close();
}

J'avoue que je ne l'ai pas écrit (je l'ai seulement converti en une méthode d'extension qui a pris un paramètre de type).

Maintenant, j'en ai besoin pour me rendre le xml sous forme de chaîne (plutôt que de l'enregistrer dans un fichier). Je l'examine, mais je ne l'ai pas encore compris.

J'ai pensé que cela pourrait être vraiment facile pour quelqu'un qui connaît ces objets. Sinon, je le découvrirai finalement.

Vaccano
la source

Réponses:

530

Utilisez un StringWriterau lieu d'un StreamWriter:

public static string SerializeObject<T>(this T toSerialize)
{
    XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());

    using(StringWriter textWriter = new StringWriter())
    {
        xmlSerializer.Serialize(textWriter, toSerialize);
        return textWriter.ToString();
    }
}

Remarque, il est important d'utiliser à la toSerialize.GetType()place de typeof(T)dans le constructeur XmlSerializer: si vous utilisez la première, le code couvre toutes les sous-classes possibles de T(qui sont valides pour la méthode), tandis que l'utilisation de la dernière échouera lors du passage d'un type dérivé de T. Voici un lien avec un exemple de code qui motive cette instruction, avec le XmlSerializerlancement d'un Exceptionquand typeof(T)est utilisé, car vous passez une instance d'un type dérivé à une méthode qui appelle SerializeObject qui est définie dans la classe de base du type dérivé: http: // ideone .com / 1Z5J1 .

De plus, Ideone utilise Mono pour exécuter le code; le réel que Exceptionvous obtiendriez en utilisant le runtime Microsoft .NET est différent Messagede celui montré sur Ideone, mais il échoue tout de même.

dtb
la source
2
@JohnSaunders: ok, c'est une bonne idée de déplacer cette discussion sur Meta. Voici le lien vers la question que je viens de publier sur Meta Stack Overflow concernant cette modification .
Fulvio
27
@casperOne Guys, veuillez cesser de jouer avec ma réponse. Le point est d'utiliser StringWriter au lieu de StreamWriter, tout le reste n'est pas pertinent pour la question. Si vous souhaitez discuter de détails tels que typeof(T) versus toSerialize.GetType(), veuillez le faire, mais pas dans ma réponse. Merci.
dtb
9
@dtb Désolé, mais Stack Overflow est édité en collaboration . En outre, cette réponse spécifique a été discutée sur la méta , donc l'édition est maintenue. Si vous n'êtes pas d'accord, veuillez répondre à ce message sur la méta expliquant pourquoi vous pensez que votre réponse est un cas spécial et ne devrait pas être modifiée en collaboration.
casperOne
2
Codewise, c'est l'exemple le plus court que j'ai vu. +1
froggythefrog
13
StringWriter implémente IDisposable, doit donc être enfermé dans un bloc using.
TrueWill
70

Je sais que ce n'est pas vraiment une réponse à la question, mais sur la base du nombre de votes pour la question et la réponse acceptée, je soupçonne que les gens utilisent réellement le code pour sérialiser un objet en chaîne.

L'utilisation de la sérialisation XML ajoute des déchets de texte supplémentaires inutiles à la sortie.

Pour la classe suivante

public class UserData
{
    public int UserId { get; set; }
}

il génère

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

La meilleure solution consiste à utiliser la sérialisation JSON (l'un des meilleurs est Json.NET ). Pour sérialiser un objet:

var userData = new UserData {UserId = 0};
var userDataString = JsonConvert.SerializeObject(userData);

Pour désérialiser un objet:

var userData = JsonConvert.DeserializeObject<UserData>(userDataString);

La chaîne JSON sérialisée ressemblerait à:

{"UserId":0}
xhafan
la source
4
Dans ce cas, vous avez raison mais avez-vous vu de gros documents XML et de gros documents JSON. Les documents JSON sont difficilement lisibles. Les "déchets" dont vous parlez comme les espaces de noms peuvent être supprimés. Le XML généré peut être aussi propre que JSON mais est TOUJOURS plus lisible que JSON. La lisibilité est un gros avantage par rapport à JSON.
Herman Van Der Blom,
2
Si vous recherchez en ligne «analyseur en ligne json», vous trouverez des analyseurs en ligne json qui peuvent formater la chaîne json de manière plus lisible par l'homme.
2017
9
@HermanVanDerBlom XML plus lisible que JSON? Que fumes-tu dans le monde? C'est l'un des avantages les plus forts de JSON par rapport à XML: il est beaucoup plus facile à lire en raison du rapport signal / bruit plus élevé. Autrement dit, avec JSON, le contenu ne se noie pas dans la soupe aux étiquettes!
Mason Wheeler
63

Sérialiser et désérialiser (XML / JSON):

    public static T XmlDeserialize<T>(this string toDeserialize)
    {
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
        using(StringReader textReader = new StringReader(toDeserialize))
        {      
            return (T)xmlSerializer.Deserialize(textReader);
        }
    }

    public static string XmlSerialize<T>(this T toSerialize)
    {
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
        using(StringWriter textWriter = new StringWriter())
        {
            xmlSerializer.Serialize(textWriter, toSerialize);
            return textWriter.ToString();
        }
    }

    public static T JsonDeserialize<T>(this string toDeserialize)
    {
        return JsonConvert.DeserializeObject<T>(toDeserialize);
    }

    public static string JsonSerialize<T>(this T toSerialize)
    {
        return JsonConvert.SerializeObject(toSerialize);
    }
ADMETTRE
la source
15
+1 pour montrer également comment désérialiser, contrairement à toutes les autres réponses. Merci!
deadlydog
6
Cependant, une modification mineure consisterait à renvoyer T au lieu d'objet et à convertir l'objet renvoyé en T dans la fonction DeserializeObject. De cette façon, l'objet fortement typé est retourné au lieu d'un objet générique.
deadlydog
Merci @deadlydog, j'ai corrigé.
ADM-IT
3
TextWriter a une fonction Dispose () qui doit être appelée. Vous oubliez donc les instructions Using.
Herman Van Der Blom
38

Code de sécurité

En ce qui concerne la réponse acceptée , il est important d'utiliser au toSerialize.GetType()lieu de typeof(T)dans le XmlSerializerconstructeur: si vous utilisez le premier code couvre tous les scénarios possibles, tout en utilisant ce dernier on échoue parfois.

Voici un lien avec un exemple de code qui motive cette instruction, avec le XmlSerializerlancement d'une exception lorsque typeof(T)est utilisé, car vous passez une instance d'un type dérivé à une méthode qui appelle SerializeObject<T>()qui est définie dans la classe de base du type dérivé: http: // ideone .com / 1Z5J1 . Notez que Ideone utilise Mono pour exécuter le code: l'exception réelle que vous obtiendriez en utilisant le runtime Microsoft .NET a un message différent de celui affiché sur Ideone, mais il échoue tout de même.

Par souci d'exhaustivité, je poste ici l'exemple de code complet pour référence future, juste au cas où Ideone (où j'ai posté le code) deviendrait indisponible à l'avenir:

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

public class Test
{
    public static void Main()
    {
        Sub subInstance = new Sub();
        Console.WriteLine(subInstance.TestMethod());
    }

    public class Super
    {
        public string TestMethod() {
            return this.SerializeObject();
        }
    }

    public class Sub : Super
    {
    }
}

public static class TestExt {
    public static string SerializeObject<T>(this T toSerialize)
    {
        Console.WriteLine(typeof(T).Name);             // PRINTS: "Super", the base/superclass -- Expected output is "Sub" instead
        Console.WriteLine(toSerialize.GetType().Name); // PRINTS: "Sub", the derived/subclass

        XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
        StringWriter textWriter = new StringWriter();

        // And now...this will throw and Exception!
        // Changing new XmlSerializer(typeof(T)) to new XmlSerializer(subInstance.GetType()); 
        // solves the problem
        xmlSerializer.Serialize(textWriter, toSerialize);
        return textWriter.ToString();
    }
}
Fulvio
la source
12
Vous devriez également faire using (StringWriter textWriter = new StringWriter() {}pour la fermeture / élimination appropriée de l'objet.
Amiable
Je suis entièrement d'accord avec vous @Amicable! J'ai simplement essayé de garder mon exemple de code aussi près que possible de celui de l'OP, afin de mettre en évidence mon point qui concerne les types d'objets. Quoi qu'il en soit, il est bon de se rappeler que quiconque usingest le meilleur ami à la fois pour nous et pour nos chers IDisposableobjets de mise en œuvre;)
Fulvio
"Les méthodes d'extension vous permettent" d'ajouter "des méthodes aux types existants sans créer de nouveau type dérivé, sans recompiler ou autrement modifier le type d'origine." msdn.microsoft.com/en-us/library/bb383977.aspx
Adrian
12

Mon 2p ...

        string Serialise<T>(T serialisableObject)
        {
            var xmlSerializer = new XmlSerializer(serialisableObject.GetType());

            using (var ms = new MemoryStream())
            {
                using (var xw = XmlWriter.Create(ms, 
                    new XmlWriterSettings()
                        {
                            Encoding = new UTF8Encoding(false),
                            Indent = true,
                            NewLineOnAttributes = true,
                        }))
                {
                    xmlSerializer.Serialize(xw,serialisableObject);
                    return Encoding.UTF8.GetString(ms.ToArray());
                }
            }
        }
oPless
la source
+1 pour l'utilisation de XmlWriterSettings (). Je voulais que mon XML sérialisé ne perde pas d'espace avec les jolis éléments imprimés et que le réglage Indent = false et NewLineOnAttributes = false ait fait le travail.
Lee Richardson
Merci @LeeRichardson - J'avais besoin de faire exactement le contraire, aussi XmlWriter sous .net par défaut UTF16 qui n'est pas ce que j'écrivais non plus.
oPless
utiliser cette combinaison de memorystream et l'obtenir via Encoding GetString inclura le préambule / nomenclature comme premier caractère de votre chaîne. Voir aussi stackoverflow.com/questions/11701341/…
Jamee
@Jamee "Encoding = UTF8Encoding (false)" signifie ne pas écrire la nomenclature selon docs.microsoft.com/en-us/dotnet/api/… ... ce comportement a-t-il changé depuis .net4?
oPless
4
public static string SerializeObject<T>(T objectToSerialize)
        {
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            MemoryStream memStr = new MemoryStream();

            try
            {
                bf.Serialize(memStr, objectToSerialize);
                memStr.Position = 0;

                return Convert.ToBase64String(memStr.ToArray());
            }
            finally
            {
                memStr.Close();
            }
        }

        public static T DerializeObject<T>(string objectToDerialize)
        {
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            byte[] byteArray = Convert.FromBase64String(objectToDerialize);
            MemoryStream memStr = new MemoryStream(byteArray);

            try
            {
                return (T)bf.Deserialize(memStr);
            }
            finally
            {
                memStr.Close();
            }
        }
théière
la source
1

Je n'ai pas pu utiliser la méthode JSONConvert suggérée par xhafan

Dans .Net 4.5, même après avoir ajouté la référence d'assembly "System.Web.Extensions", je n'ai toujours pas pu accéder à JSONConvert.

Cependant, une fois la référence ajoutée, vous pouvez imprimer la même chaîne en utilisant:

JavaScriptSerializer js = new JavaScriptSerializer();
string jsonstring = js.Serialize(yourClassObject);
Thomas Tiveron
la source
2
La classe JSONConvert se trouve dans l'espace de noms NewtonSoft.Json. Accédez au gestionnaire de packages dans votre VS, puis téléchargez le package NewtonSoft.Json
Amir Shrestha
1

Je me sentais comme si j'avais besoin de partager ce code manipulé à la réponse acceptée - comme je n'ai pas de réputation, je ne peux pas commenter ..

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

namespace ObjectSerialization
{
    public static class ObjectSerialization
    {
        // THIS: (C): /programming/2434534/serialize-an-object-to-string
        /// <summary>
        /// A helper to serialize an object to a string containing XML data of the object.
        /// </summary>
        /// <typeparam name="T">An object to serialize to a XML data string.</typeparam>
        /// <param name="toSerialize">A helper method for any type of object to be serialized to a XML data string.</param>
        /// <returns>A string containing XML data of the object.</returns>
        public static string SerializeObject<T>(this T toSerialize)
        {
            // create an instance of a XmlSerializer class with the typeof(T)..
            XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());

            // using is necessary with classes which implement the IDisposable interface..
            using (StringWriter stringWriter = new StringWriter())
            {
                // serialize a class to a StringWriter class instance..
                xmlSerializer.Serialize(stringWriter, toSerialize); // a base class of the StringWriter instance is TextWriter..
                return stringWriter.ToString(); // return the value..
            }
        }

        // THIS: (C): VPKSoft, 2018, https://www.vpksoft.net
        /// <summary>
        /// Deserializes an object which is saved to an XML data string. If the object has no instance a new object will be constructed if possible.
        /// <note type="note">An exception will occur if a null reference is called an no valid constructor of the class is available.</note>
        /// </summary>
        /// <typeparam name="T">An object to deserialize from a XML data string.</typeparam>
        /// <param name="toDeserialize">An object of which XML data to deserialize. If the object is null a a default constructor is called.</param>
        /// <param name="xmlData">A string containing a serialized XML data do deserialize.</param>
        /// <returns>An object which is deserialized from the XML data string.</returns>
        public static T DeserializeObject<T>(this T toDeserialize, string xmlData)
        {
            // if a null instance of an object called this try to create a "default" instance for it with typeof(T),
            // this will throw an exception no useful constructor is found..
            object voidInstance = toDeserialize == null ? Activator.CreateInstance(typeof(T)) : toDeserialize;

            // create an instance of a XmlSerializer class with the typeof(T)..
            XmlSerializer xmlSerializer = new XmlSerializer(voidInstance.GetType());

            // construct a StringReader class instance of the given xmlData parameter to be deserialized by the XmlSerializer class instance..
            using (StringReader stringReader = new StringReader(xmlData))
            {
                // return the "new" object deserialized via the XmlSerializer class instance..
                return (T)xmlSerializer.Deserialize(stringReader);
            }
        }

        // THIS: (C): VPKSoft, 2018, https://www.vpksoft.net
        /// <summary>
        /// Deserializes an object which is saved to an XML data string.
        /// </summary>
        /// <param name="toDeserialize">A type of an object of which XML data to deserialize.</param>
        /// <param name="xmlData">A string containing a serialized XML data do deserialize.</param>
        /// <returns>An object which is deserialized from the XML data string.</returns>
        public static object DeserializeObject(Type toDeserialize, string xmlData)
        {
            // create an instance of a XmlSerializer class with the given type toDeserialize..
            XmlSerializer xmlSerializer = new XmlSerializer(toDeserialize);

            // construct a StringReader class instance of the given xmlData parameter to be deserialized by the XmlSerializer class instance..
            using (StringReader stringReader = new StringReader(xmlData))
            {
                // return the "new" object deserialized via the XmlSerializer class instance..
                return xmlSerializer.Deserialize(stringReader);
            }
        }
    }
}

Petteri Kautonen
la source
Je sais que c'est vieux, mais comme vous avez donné une très bonne réponse, j'ajouterai un petit commentaire, comme si j'avais fait une revue de code sur un PR: vous devriez avoir des contraintes sur T lorsque vous utilisez des génériques. Cela aide à garder les choses propres, et tous les objets dans une base de code et des cadres référencés ne se prêtent pas à la sérialisation
Frank R. Haugen
-1

Dans certains cas rares, vous souhaiterez peut-être implémenter votre propre sérialisation de chaîne.

Mais c'est probablement une mauvaise idée à moins que vous sachiez ce que vous faites. (par exemple, sérialisation des E / S avec un fichier de commandes)

Quelque chose comme ça ferait l'affaire (et il serait facile de le modifier à la main / par lot), mais faites attention à ce que d'autres vérifications soient effectuées, comme si ce nom ne contient pas de nouvelle ligne.

public string name {get;set;}
public int age {get;set;}

Person(string serializedPerson) 
{
    string[] tmpArray = serializedPerson.Split('\n');
    if(tmpArray.Length>2 && tmpArray[0].Equals("#")){
        this.name=tmpArray[1];
        this.age=int.TryParse(tmpArray[2]);
    }else{
        throw new ArgumentException("Not a valid serialization of a person");
    }
}

public string SerializeToString()
{
    return "#\n" +
           name + "\n" + 
           age;
}
satibel
la source
-1

[VB]

Public Function XmlSerializeObject(ByVal obj As Object) As String

    Dim xmlStr As String = String.Empty

    Dim settings As New XmlWriterSettings()
    settings.Indent = False
    settings.OmitXmlDeclaration = True
    settings.NewLineChars = String.Empty
    settings.NewLineHandling = NewLineHandling.None

    Using stringWriter As New StringWriter()
        Using xmlWriter__1 As XmlWriter = XmlWriter.Create(stringWriter, settings)

            Dim serializer As New XmlSerializer(obj.[GetType]())
            serializer.Serialize(xmlWriter__1, obj)

            xmlStr = stringWriter.ToString()
            xmlWriter__1.Close()
        End Using

        stringWriter.Close()
    End Using

    Return xmlStr.ToString
End Function

Public Function XmlDeserializeObject(ByVal data As [String], ByVal objType As Type) As Object

    Dim xmlSer As New System.Xml.Serialization.XmlSerializer(objType)
    Dim reader As TextReader = New StringReader(data)

    Dim obj As New Object
    obj = DirectCast(xmlSer.Deserialize(reader), Object)
    Return obj
End Function

[C #]

public string XmlSerializeObject(object obj)
{
    string xmlStr = String.Empty;
    XmlWriterSettings settings = new XmlWriterSettings();
    settings.Indent = false;
    settings.OmitXmlDeclaration = true;
    settings.NewLineChars = String.Empty;
    settings.NewLineHandling = NewLineHandling.None;

    using (StringWriter stringWriter = new StringWriter())
    {
        using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings))
        {
            XmlSerializer serializer = new XmlSerializer( obj.GetType());
            serializer.Serialize(xmlWriter, obj);
            xmlStr = stringWriter.ToString();
            xmlWriter.Close();
        }
    }
    return xmlStr.ToString(); 
}

public object XmlDeserializeObject(string data, Type objType)
{
    XmlSerializer xmlSer = new XmlSerializer(objType);
    StringReader reader = new StringReader(data);

    object obj = new object();
    obj = (object)(xmlSer.Deserialize(reader));
    return obj;
}
Brian
la source