0

I am trying to create an object structure like this Universe - contains a collection of Worlds Worlds contains a collection of areas.

All 3 of these types extend from a base class which has a property of 'ParentObject'. Worlds will reference the Universe via this property. Areas will reference the world via this property.

When running a test harness application.

            Universe universe = Universe.GetInstance();
            var world = universe.CreateWorld();
            var area = world.CreateArea();

            area.UpdateIpfs();

UpdateIpfs on the area object - then recurses upwards from area to world to universe - serializing each tier and then adding to the Ipfs network. (Thats just where I am storing the json data)

In order to get the objects back I do

            Universe universe = Universe.GetInstance("QmZnaSaDNnmqhUrE8kFHeu9PGGStAr2D4q3Vt88yHwFvzG");
            var world = universe.Worlds[0];
            var area = world.Areas[0];

Stepping through the code I can see the json content is this before deserialization:

{
  "$id": "1",
  "ParentObject": null,
  "Worlds": [
    {
      "$id": "2",
      "ParentObject": {
        "$ref": "1"
      },
      "Time": "2019-10-06T23:13:56.6002056+01:00",
      "Name": null,
      "Description": null,
      "Areas": [
        {
          "$id": "3",
          "EventScripts": {
            "$id": "4"
          },
          "ParentObject": {
            "$ref": "2"
          },
          "Name": null,
          "IsInterior": false,
          "IsUnderground": false,
          "IsArtificial": false,
          "Description": null,
          "X": 0,
          "Y": 0,
          "Z": 0,
          "AreaObjects": [],
          "ObjType": "BloodlinesLib.Engine.World.Area, BloodlinesLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
          "Hash": "QmbM86GZn6w9JadBM143fDYwGisgNPGXke3bFxpXzrfgJh"
        }
      ],
      "ObjType": "BloodlinesLib.Engine.World.World, BloodlinesLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
      "Hash": "QmRnEfndUDjCTifY694YuftPNWSRHQQJn9WwZmSpUBRJmv"
    }
  ],
  "ObjType": "BloodlinesLib.Engine.World.Universe, BloodlinesLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null",
  "Hash": null
}

I can see that the ParentObject for the area object is $ref: 2 Which seems to match the Id of the World object.

But when I deserialize - the ParentObject on the 'Area' object is equal to Null. However the ParentObject on World object is correctly assigned.

Anyone have any ideas why my ParentObject is not being deserialized properly for the Area's ParentObject.

My serialization and deserialize code is here:

public void UpdateIpfs()
        {

            JsonSerializerSettings sets = new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                ContractResolver = new NonPublicPropertiesResolver()
            };

            var obj = Convert.ChangeType(this, ObjType);

            if(ObjType == typeof(Universe))
            {
                Hash = null;
            }
            var strContent = JsonConvert.SerializeObject(obj, Formatting.Indented, sets);

            var ipfs = new IpfsClient();
            byte[] data = Encoding.UTF8.GetBytes(strContent);
            data = CompressionHelper.Compress(data);
            using (MemoryStream ms = new MemoryStream(data))
            {
                Hash = ipfs.FileSystem.AddAsync(ms).Result.Id.Hash.ToString();
            }
            if(ParentObject != null)
            {
                ParentObject.UpdateIpfs();
            }
            OnUpdate?.Invoke(this);
        }



and

public static T LoadFromIpfs<T>(string hash)
        {
            JsonSerializerSettings sets = new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                ContractResolver = new NonPublicPropertiesResolver()
            };

            var ipfs = new IpfsClient();
            byte[] data;
            using (MemoryStream ms = new MemoryStream())
            {
                ipfs.FileSystem.ReadFileAsync(hash).Result.CopyTo(ms);
                data =ms.ToArray();
            }
            data = CompressionHelper.Decompress(data);

            string content = Encoding.UTF8.GetString(data);
            T obj = JsonConvert.DeserializeObject<T>(content, sets);
            return obj;
        }

Universe.cs




public class IpfsObject
    {
        public IpfsObject ParentObject;
        public Type ObjType { get; set; }

        public string Hash { get; set; }


        public static T LoadFromIpfs<T>(string hash)
        {
            JsonSerializerSettings sets = new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.Objects
            };

            var ipfs = new IpfsClient();
            byte[] data;
            using (MemoryStream ms = new MemoryStream())
            {
                ipfs.FileSystem.ReadFileAsync(hash).Result.CopyTo(ms);
                data =ms.ToArray();
            }
            data = CompressionHelper.Decompress(data);

            string content = Encoding.UTF8.GetString(data);
            T obj = JsonConvert.DeserializeObject<T>(content, sets);
            return obj;
        }



        public delegate void OnUpdateDelegate(object obj);
        public event OnUpdateDelegate OnUpdate;

        public void UpdateIpfs()
        {

            JsonSerializerSettings sets = new JsonSerializerSettings
            {
                PreserveReferencesHandling = PreserveReferencesHandling.Objects
            };

            var obj = Convert.ChangeType(this, ObjType);

            if(ObjType == typeof(Universe))
            {
                Hash = null;
            }
            var strContent = JsonConvert.SerializeObject(obj, Formatting.Indented, sets);

            var ipfs = new IpfsClient();
            byte[] data = Encoding.UTF8.GetBytes(strContent);
            data = CompressionHelper.Compress(data);
            using (MemoryStream ms = new MemoryStream(data))
            {
                Hash = ipfs.FileSystem.AddAsync(ms).Result.Id.Hash.ToString();
            }
            if(ParentObject != null)
            {
                ParentObject.UpdateIpfs();
            }
            OnUpdate?.Invoke(this);
        }
    }



    public class Universe : IpfsObject
    {
        public Universe()
        {
            Worlds = new List<World>();
            this.ObjType = typeof(Universe);
            OnUpdate += Universe_OnUpdate;
        }

        private void Universe_OnUpdate(object obj)
        {
            IpfsObject ipfsObj = obj as IpfsObject;
            Console.WriteLine("Universe updated: "+ ipfsObj.Hash);
            File.WriteAllText("UniverseHash.txt", ipfsObj.Hash);
        }

        public World CreateWorld()
        {
            World world = new World(this);
            Worlds.Add(world);
            return world;
        }
        public List<World> Worlds { get; set; }






        public static Universe GetInstance(string hash = null)
        {
            if(_universe == null)
            {
                if(hash == null)
                {
                    _universe = new Universe();
                }
                else
                {
                    _universe = IpfsObject.LoadFromIpfs<Universe>(hash);
                    _universe.Hash = hash;
                }
            }
            return _universe;
        }
        private static Universe _universe;


    }

    public class World : Ipfs.IpfsObject
    {
        public World(Universe universe)
        {
            Time = DateTime.Now;
            ParentObject = universe;
            this.ObjType = typeof(World);
            Areas = new List<Area>();
        }

        public Area CreateArea(bool findFreespace = false)
        {
            Area area = new Area(this);
            Areas.Add(area);
            if (findFreespace)
            {
                bool b = false;
                Random rand = new Random(Guid.NewGuid().GetHashCode());
                while (!b)
                {
                    b = area.SetPosition(rand.Next(-500, 500), rand.Next(-500, 500), 0);
                }
            }
            area.UpdateIpfs();
            return area;
        }

        public void SetName(string name)
        {
            Name = name;
            UpdateIpfs();
        }
        public void SetDescription(string desc)
        {
            Description = desc;
            UpdateIpfs();
        }

        public DateTime Time { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public List<Area> Areas { get; set; }


    }



    public class Area : IpfsObject
    {
        public Area(World worldParent)
        {
            this.ObjType = typeof(Area);
            ParentObject = worldParent;
            AreaObjects = new List<AreaObject>();

        }

        public string Name { get; private set; }
        public bool IsInterior { get; private set; }
        public bool IsUnderground { get; private set; }
        public bool IsArtificial { get; private set; }
        public string Description { get; private set; }


        public void SetName(string name)
        {
            Name = name;
            UpdateIpfs();
        }
        public void SetDescription(string desc)
        {
            Description = desc;
            UpdateIpfs();
        }

        public void SetInterior(bool interior)
        {
            IsInterior = interior;
            UpdateIpfs();
        }
        public void SetUnderground(bool underground)
        {
            IsUnderground = underground;
            UpdateIpfs();
        }
        public void SetArtificial(bool artificial)
        {
            IsArtificial = artificial;
            UpdateIpfs();
        }


        public int X { get; set; }
        public int Y { get; set; }
        public int Z { get; set; }

        public bool SetPosition(int x,int y, int z)
        {
            World world = (World)ParentObject;
            var area = world.Areas.FirstOrDefault(e => e.X == x && e.Y == y && e.Z == z);
            if(area != null)
            {
                return false;
            }
            this.X = x;
            this.Y = y;
            this.Z = z;
            UpdateIpfs();
            return true;
        }


    }

Baaleos
  • 1,703
  • 12
  • 22
  • 1
    To help you with this sort of problem, we'll need to see a minimal c# data model that reproduces the problem. For instance, `PreserveReferencesHandling.Objects` does not work for objects with parameterized constructors, see [Deserialization of self-referencing properties does not work](https://stackoverflow.com/a/6303647). – dbc Oct 06 '19 at 22:30
  • It also doesn't work when there is a custom `JsonConverter`, see [Custom object serialization vs PreserveReferencesHandling](https://stackoverflow.com/q/53695270/3744182). A [mcve] would likely make clear where your problem lies. – dbc Oct 06 '19 at 22:33
  • Updated with the 3 classes – Baaleos Oct 06 '19 at 22:39
  • I don't see where `ParentObject` is defined. Can you simplify that down into a [mcve] that could be compiled in, say, a console or https://dotnetfiddle.net/ app? – dbc Oct 06 '19 at 22:46
  • Now updated with the 4 classes IpfsObject baseclass is where ParentObject is defined. Forgot to mention- this is .net core 2.2 – Baaleos Oct 06 '19 at 22:58
  • I am guessing the universe to world relationship works because the universe has no parameters in the constructor. – Baaleos Oct 06 '19 at 23:28

1 Answers1

0

I can confirm that when I take the parameters out of the constructor for World/Area - that the deserialisation then preserves the reference link between world and area.

Baaleos
  • 1,703
  • 12
  • 22