C# Object nur einmal deserialisieren

Drexel

Lt. Commander
Registriert
Jan. 2012
Beiträge
1.794
Hallo zusammen,

Zum besseren Verständnis: Ich schreib mir gerade nen Youtube Uploader und habe eine Liste von Videos denen jeweils ein Projekt-Template zugeordnet ist. Videos einer Reihe ist immer das gleiche Template Objekt zugeordnet. Wenn ich das ganze jetzt aber mit JSONSerializer serialisiere und danach wieder deserialisiere, hat jedes Video sein eigenes Template Objekt, die aber alle inhaltsgleich sind. Krieg ich den Deserializer irgendwie dazu, aus dem was vorher nur eine Objektinstanz war bei deserialisieren auch wieder nur eine Objektinstanz zu machen? Oder muss ich es nacher aufräumen oder mit einem Converter oder so eingreifen?

Code:
    public class Upload
    {
        private Guid guid;
        private string filePath;
        private Template template;
        ...

        public Upload()
        {

        }

        public Upload(string filePath)
        {
            this.guid = Guid.NewGuid();
            this.filePath = filePath;
        }

        public Guid Guid { get => guid; }
        public string FilePath { get => filePath; set => filePath = value; }
        public Template Template { get => template; set => template = value; }
    }

public class Template
    {
        private Guid guid;
        private string name;
        ...

        public Template()
        {

        }
        public Template(string name)
        {
            this.guid = Guid.NewGuid();
            this.name = name;
        }

        public Guid Guid { get => guid; }
        public string Name { get => name; set => name = value; }
    }

    public class UploadList
    {
        private List<Upload> uploads;

        public List<Upload> Uploads { get => uploads; }

        public UploadList()
        {
            Template t1 = new Template("P1");
            Template t2 = new Template("P2");
            uploads = new List<Upload>();
            uploads.Add(new Upload(@"E:\P1 01.mkv") { Template = t1 });
            uploads.Add(new Upload(@"E:\P1 02.mkv") { Template = t1 });
            uploads.Add(new Upload(@"E:\P2 01.mkv") { Template = t2 });
            uploads.Add(new Upload(@"E:\P2 02.mkv") { Template = t2 });
            uploads.Add(new Upload(@"E:\P2 02.mkv") { Template = t2 });
            uploads.Add(new Upload(@"E:\P1 03.mkv") { Template = t1 });

            if(uploads[0].Template == uploads[1].Template)
            {
                //true
            }
            JsonSerializerOptions options = new JsonSerializerOptions();
            options.WriteIndented = true;
            options.Converters.Add(new JsonStringEnumConverter());
            string jsonString;
            jsonString = JsonSerializer.Serialize(uploads, options);
            File.WriteAllText(@"d:\uploads.json", jsonString);

            List<Upload> uploads2 = JsonSerializer.Deserialize<List<Upload>>(jsonString);
            if (uploads2[0].Template == uploads2[1].Template)
            {
                //false
            }
        }
    }
}
 
Zuletzt bearbeitet:
Das musst du manuell selbst erledigen. JSON kennt den Typ "Referenz" einfach nicht. Entweder du sorgst beim Serialisieren dafür, dass Referenzen auch als solche hinterlegt werden oder du musst es manuell nach dem Deserialisieren machen.
 
  • Gefällt mir
Reaktionen: Drexel
Auch für Dich: in einen Konstruktor gehört KEINE Programmierlogik!

Wozu die GUID überall? Werden die irgendwo gebraucht? Sieht mir grad sehr willkürlich aus.

In Ableitung des vorliegenden Codes würde ich einen Serializer basteln für UploadList und in die JSON Daten nur die Liste der Dateien schreiben. Für die Deserialisierung dann neue Uploadobjekte instantiieren und diese in eine neue UploadList packen.

ps A: du erstellst Templateobjekte mit einem String als Argument, hast aber nur Konstruktore für () und (String,String).

ps B: Template == Template macht nur dann, was Du willst, wenn Du IEquatable in Template passend implementierst. Ansonsten kriegst Du da referentielle Identität raus, sodaß für zwei instantiierte Objekte immer false zurückkommt.
 
  • Gefällt mir
Reaktionen: Drexel
Yuuri schrieb:
...Entweder du sorgst beim Serialisieren dafür, dass Referenzen auch als solche hinterlegt werden...
Was genau meinst Du damit?

RalphS schrieb:
Auch für Dich: in einen Konstruktor gehört KEINE Programmierlogik!
Ich denke Du meinst den UploadList Konstruktor oder? Der bleibt natürlich nicht so, das war nur schnell hingefrickelt um das erste Serialisieren auf u.a. diesen Sachverhalt zu testen...

RalphS schrieb:
Wozu die GUID überall? Werden die irgendwo gebraucht? Sieht mir grad sehr willkürlich aus.
Z.B. um jetzt beim Deserialisieren die Instanzen festzustellen....

RalphS schrieb:
In Ableitung des vorliegenden Codes würde ich einen Serializer basteln für UploadList und in die JSON Daten nur die Liste der Dateien schreiben. Für die Deserialisierung dann neue Uploadobjekte instantiieren und diese in eine neue UploadList packen.
Bin mir nicht sicher was Du damit sagen willst bzw was mir das bringt.

RalphS schrieb:
ps A: du erstellst Templateobjekte mit einem String als Argument, hast aber nur Konstruktore für () und (String,String).
Der Code ist natürlich gekürzt, den Konstruktor habe ich wohl übersehen, ist korrigiert...

RalphS schrieb:
ps B: Template == Template macht nur dann, was Du willst, wenn Du IEquatable in Template passend implementierst. Ansonsten kriegst Du da referentielle Identität raus, sodaß für zwei instantiierte Objekte immer false zurückkommt.
Es war aber an der Stelle genau mein Ziel zu prüfen, ob es die gleiche Instanz ist. Wie gesagt, das ist nur Testcode, der nicht lange überleben wird...
 
Drexel schrieb:
Was genau meinst Du damit?
JSON kennt keine Referenzen.

Aus
Code:
const a = {name:"a"}
const b = {name:"b"}
const list = [a,b,b,a]
wird immer ein
Code:
[ { name: 'a' }, { name: 'b' }, { name: 'b' }, { name: 'a' } ]
werden, obwohl a und b jeweils nur auf eine Instanz verweisen.

Prüfst du die Objekte
Code:
list[0] === list[3] // => true
ist es die selbe Instanz.

Serialisierst und Deserialisierst du das Ganze nun
Code:
const serialized = JSON.stringify( list )
const deserialized = JSON.parse( serialized )

deserialized[0] === deserialized[3] // => false
sind es zwei eigenständige Objekte, obwohl sie eigentlich gleich sind.

Heißt du musst die Referenzierung selbst vornehmen. Bau dir irgendwo ne Liste wo du die Objekte sammelst und in den Werten berufst du dich dann nur auf die Referenzen.

Irgendwie so:
Code:
{
  "$refs": {
    "ref#0": {name: "a"},
    "ref#1": {name: "b"}
  },
  "values": [
    {ref: "ref#0"},
    {ref: "ref#1"},
    {ref: "ref#1"},
    {ref: "ref#0"}
  ]
}
Musst beim Deserialisieren dann halt diese Struktur auseinander nehmen und in deine überführen.

Beispielimplementierung in JS:
Code:
const testEquality = (a,b) => a === b

const a = {name:"a"}
const b = {name:"b"}

const list = [a,b,b,a]

console.log( {"test references": testEquality( list[0], list[3] )} )
// => true, da selbe Instanz

const serialized = JSON.stringify( list )
const deserialized = JSON.parse( serialized )

console.log( {"test deserialized references": testEquality( deserialized[0], deserialized[3] )} )
// => false, da unterschiedliche Instanz

const referencedSerialize = function( values )
{
  const map = {
    "$refs": {},
    "values": []
  }

  const getReferenceTo = function( key )
  {
    return {ref: key}
  }

  const isFoundInRefs = function( value )
  {
    for( const refKey of Object.keys( map["$refs"] ) )
    {
      const ref = map["$refs"][refKey]
      if( value === ref )
      {
        return getReferenceTo( refKey )
      }
    }
    return null
  }

  const addReference = function( value )
  {
    const referenceCount = Object.keys( map["$refs"] ).length + 1
    const referenceKey = "ref#" + referenceCount
    map["$refs"][referenceKey] = value
    const reference = getReferenceTo( referenceKey )
    return reference
  }

  for( const value of values )
  {
    map["values"].push( isFoundInRefs( value ) || addReference( value ) )
  }

  return JSON.stringify( map )
}

const serializedReferences = referencedSerialize( list )
// console.log( {"serialized references": serializedReferences} )

const referencedDeserialize = function( json )
{
  const map = JSON.parse( json )
  let values = []

  const getReferenceFrom = function( key )
  {
    return map["$refs"][key]
  }

  for( const referencedValue of map["values"] )
  {
    const referenceKey = referencedValue["ref"]
    values.push( getReferenceFrom( referenceKey ) )
  }

  return values
}

const deserializedReferences = referencedDeserialize( serializedReferences )
// console.log( {"deserialized with references": deserializedReferences} )

console.log( {"test references": testEquality( deserializedReferences[0], deserializedReferences[3] )} )
// => true, da selbe Instanz
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Drexel
Achso hast Du das gemeint, ja wie gesagt ich muss mal schauen ob ich das evtl mit Convertern schon während des Deserialisierens hinbekomme. Ich werde beim Serialisieren im Videoobjekt wahrscheinlich nicht das ganze Templates Objekt mit Serialisieren sondern nur die Guid und die dann optimalerweise beim Deserialisierens wieder durch das entsprechende Templates Objekt ersetzen. Muss ich mir aber noch genau anschauen.
 
So für die Nachwelt: Ich habe es jetzt so gelöst, dass ich mir Templates und Uploads beim Deserialisieren in einem Repository zwischenspeichere und mit einem Converter verhinder, dass ein Template Objekt mehrfach deserialisert wird, wenn ich Uploads lade:


Code:
    public class TemplateConverter : JsonConverter<Template>
    {
        public override Template Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            Guid templateGuid = Guid.Parse(reader.GetString());
            return SerializationRepository.Templates[templateGuid];
        }

        public override void Write(Utf8JsonWriter writer, Template value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.Guid);
        }
    }

 public class JsonSerialialization
    {

        public static UploadList DeserializeUploadList()
        {
            if (!File.Exists(JsonSerialialization.uploadFilePath))
            {
                return new UploadList(new List<Upload>());
            }

            string json = File.ReadAllText(uploadFilePath);
            if(string.IsNullOrWhiteSpace(json))
            {
                return new UploadList(new List<Upload>());
            }

            JsonSerializerOptions options = new JsonSerializerOptions();
            options.Converters.Add(new JsonStringEnumConverter());
            options.Converters.Add(new TemplateConverter());
            UploadList list = JsonSerializer.Deserialize<UploadList>(json, options);

            return list;
        }

        public static void SerializeUploadList(UploadList uploadList)
        {
            JsonSerializerOptions options = new JsonSerializerOptions();
            options.WriteIndented = true;
            options.Converters.Add(new JsonStringEnumConverter());
            options.Converters.Add(new TemplateConverter());
            File.WriteAllText(uploadFilePath, JsonSerializer.Serialize(uploadList, options));
        }
}
 
Zurück
Oben