XML serialisaton. Error: XmlException: Root element is missing.

MaksimMaltsev

Member
Joined
Mar 29, 2021
Messages
9
Programming Experience
1-3
Hi all. I am making a game like minecraft. I need to keep the world when the player exits the game. Chunk (it is a place 16 by 16 blocks) and world contain a lot datas, that don't serialize can. so i have created classes WorldData and ChunkData, that contain only serializable varibles. But i have a error when world is serialized: XmlException: Root element is missing. This is the code:

WorldData:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.IO;
using System.Xml.Serialization;

public class WorldData{
    public static XmlSerializableDictionary<Vector2, ChunkData> bloksData = new XmlSerializableDictionary<Vector2, ChunkData>();//deserialized dictionary
    public static XmlSerializableDictionary<VectorConventer, ChunkData> bloksConvertData = new XmlSerializableDictionary<VectorConventer, ChunkData>();//serializible dictionary

    //get deserialized world <Vector2, ChunkData>
    public static XmlSerializableDictionary<Vector2, ChunkData> Deserialization(){
        XmlSerializer WorldXML = new XmlSerializer(typeof(XmlSerializableDictionary<VectorConventer, ChunkData>));
        using (FileStream fs = new FileStream("New_World.xml", FileMode.OpenOrCreate)){
            bloksConvertData = (XmlSerializableDictionary<VectorConventer, ChunkData>)WorldXML.Deserialize(fs); //Error. XmlException: Root element is missing.
        }
        foreach(var item in bloksData){
            bloksConvertData[new VectorConventer(item.Key)] = item.Value;
        }
        return bloksData;
    }

    //serialisation
    static void SaveWorld(){
        foreach(var item in bloksData){
            VectorConventer keyVec = new VectorConventer(item.Key);
            bloksConvertData[keyVec] = item.Value;
        }
        XmlSerializer WorldXML = new XmlSerializer(typeof(XmlSerializableDictionary<VectorConventer, ChunkData>));
        using (FileStream fs = new FileStream("New_World.xml", FileMode.OpenOrCreate)){
            WorldXML.Serialize(fs, bloksConvertData);
        }
    }

    //add new chunk to save
    public static void AddNewChunk(Vector2 position){
        bloksData.Add(position, new ChunkData());
    }
}
CunkData:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using System.IO;
using System.Runtime.Serialization;
using System.Xml.Serialization;


[Serializable]
public class ChunkData
{
    public ChunkData(){}
    public BlockType [] bloks = new BlockType [165888];
    public bool isGenetated;
    
    public float posX;
    public float posZ;
    
    public void SetValues(BlockType [ , , ] bloks3d, Vector2 position){
        bloks = ToSimpleArray(bloks3d);
        posX = position.x;
        posZ = position.y;
    }

    public BlockType [ , , ] GetArray(){
        return To3dArray(bloks);
    }
    //to three dimensional array
    private static BlockType [ , , ] To3dArray(BlockType [] Array){
        BlockType [ , , ] resultArray = new BlockType [18, 512, 18];
        for(int i = 0; i < 18; i++){
            for(int j = 0; j < 512; j++){
                for(int k = 0; k < 18; k++){
                    resultArray[i, j, k] = Array[i*512*18 + j*18 + k];
                }
            }
        }
        return resultArray;
    }
    //to one dimensional array
    private static BlockType [] ToSimpleArray(BlockType [ , , ] Array)
    {
        BlockType [] resultArray = new BlockType [18 * 512 * 18];
        int index = 0;
        BlockType [] SimpleArray = new BlockType [27];
        for(int i = 0; i < 18; i++){
            for(int j = 0; j < 512; j++){
                for(int k = 0; k < 18; k++){
                    SimpleArray[index] = Array[i, j, k];
                    index++;
                }
            }
        }
        return resultArray;
    }
}
World:
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using UnityEngine;

public class World : MonoBehaviour
{
    public static Dictionary<Vector2, Chunck> chunks = new Dictionary<Vector2, Chunck>();

    public List <Vector2> toGenerate;
    public List <Vector2> toDelete;
    public static XmlSerializableDictionary<Vector2, Chunck> loadedChuncks = new XmlSerializableDictionary<Vector2, Chunck>();
    //<...>

    void Start() //called when the game starts
    {
        LoadWorld();
        //<...>
    }

    public void LoadWorld(){
        Dictionary<Vector2, ChunkData> world = WorldData.Deserialization();
        foreach (var item in world)
        {
            loadedChuncks.Add(item.Key, new Chunck());
            loadedChuncks[item.Key].Data = item.Value;
            loadedChuncks[item.Key].SetValues();
        }
    }
    //<...>
}
Chunck:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Chunck : MonoBehaviour
{
    public ChunkData Data;
    public BlockType [ , , ] bloks  = new BlockType [18, 512, 18]; //data of the blocks in the chunk
    
    public Vector2 Position = new Vector2();
    //<...>

    //generation
    public void ChunckGeneration(){
        //<...>
    }

    public void Optimize() //build voxels
    {   
        //<...>
    }

    //set data to serialisation
    void ChunckToSave(){
        Data.SetValues(this.bloks, Position);
    }

    //get data
    public void SetValues(){
        this.bloks = Data.GetArray();
        this.isGenetated = false;
    }
}
 
Chunk (it is a place 16 by 16 blocks) and world contain a lot datas, that don't serialize can.
To me, that is a code smell that you are mixing in live View information in along with your Data model. Unless you have cycical references within your Data model, it should be able to serialize.
 
Anyway, have you set a breakpoint at the end of your SaveWorld() method? Have you inspected the contents of your "New_world.xml" file right after the save operation? Does it have a root node?
 
Open the xml file in VS, you should quickly see the tree structure and problem with single root node. If the document isn't formatted with indenting you can do it with Edit > Advanced > Format Document to easier see the structure.
 
Just had a quick look at the code, in serialization you use the above FileMode.OpenOrCreate. When you have an existing file new content may only partially overwrite it, for example if existing content was "abc" and you opened it and wrote "Z", the file would now contain "Zbc". Since serialization writes complete content (and in xml case single root) you should use FileMode.Create which creates a new file or completely overwrites the existing.
 
If you haven't saved data yet, then you can't deserialize. Doesn't look like you're checking, but assume there is data on start.
You're also using FileMode.OpenOrCreate on deserialization, so if file doesn't exist it creates an empty one and crashes when attempting to read the empty file.
 
You're also using FileMode.OpenOrCreate on deserialization
Now I am already using FileMode.Create. But I vorgot to call function SaveWorld when quitting the game. Now i fixed it and have this in my xml file:
New_World:
<?xml version="1.0"?>
<Dictionary />
It's no already empty, but no data from the dictionary is there. So the error remained. What am I doing wrong?
 
Debug the save method, is there actually any data going through that foreach loop?
 
Debug the save method, is there actually any data going through that foreach loop?
Yes, there are a datas of a chunks (I am debugging heir coordinates). But I vorgt add chunks to WorldData. Now I have this in my xml file:
New_World:
<?xml version="1.0"?>
<Dictionary>
  <item>
    <key>
      <VectorConventer xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <x>-80</x>
        <y>-80</y>
      </VectorConventer>
    </key>
    <value>
      <ChunkData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <bloks>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
            .........
            <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
          <v>Air</v>
        </bloks>
        <isGenetated>false</isGenetated>
        <posX>0</posX>
        <posZ>0</posZ>
      </ChunkData>
    </value>
  </item>
</Dictionary>
looks like the algorithm for saving the chunk after generation does not work correctly, but now it's not important. The xml file size is 431,3 mb. It's too big, and there are only one chunk that has coordinates -80 -80, but I need to save ~200 chunks. Need to reduce stze of xml file. If I use char, not enum, does it help? Can write dictionary, that seves instead of many elements only their number and value? How to do it? Now XmlSerializableDictionary script contains this code:
XmlSerializableDictionary:
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
 
    [XmlRoot("Dictionary")]
public class XmlSerializableDictionary<TKey, TValue>
    : Dictionary<TKey, TValue>, IXmlSerializable
{
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }
 
    public void ReadXml(System.Xml.XmlReader reader)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
        bool wasEmpty = reader.IsEmptyElement;
        reader.Read();
        if (wasEmpty)
            return;
        while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
        {
            reader.ReadStartElement("item");
            reader.ReadStartElement("key");
            TKey key = (TKey)keySerializer.Deserialize(reader);
            reader.ReadEndElement();
            reader.ReadStartElement("value");
            TValue value = (TValue)valueSerializer.Deserialize(reader);
            reader.ReadEndElement();
            this.Add(key, value);
            reader.ReadEndElement();
            reader.MoveToContent();
        }
        reader.ReadEndElement();
    }
 
    public void WriteXml(System.Xml.XmlWriter writer)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
        foreach (TKey key in this.Keys)
        {
            writer.WriteStartElement("item");
            writer.WriteStartElement("key");
            keySerializer.Serialize(writer, key);
            writer.WriteEndElement();
            writer.WriteStartElement("value");
            TValue value = this[key];
            valueSerializer.Serialize(writer, value);
            writer.WriteEndElement();
            writer.WriteEndElement();
        }
    }
}
 
XML is a very verbose serialization format. Why can't you use a binary serializer instead?
 
Json may generate 1/10th of filesize of xml, even smaller than BinaryFormatter serialization, and file still be human readable as text. Serialize JSON to a file
If you need even smaller files just add compression (GZipStream or DeflateStream).
Json is also by far the fastest of the three.
 
If the data is repetitive, it will compress well.
 
Back
Top Bottom