Search code examples
unity-game-enginejson.netbson

Unity Json.net bson Self referencing loop


I am trying to save content in my game with Json.net. with this resource I got my game saving to JSON but now I want to save it in the Bson format as I don't want my players to be able to easily edit the save files.

Here is the code works and is saving my game data to json.

File.WriteAllText(path, JsonConvert.SerializeObject(objectToSave, Formatting.Indented,
    new JsonSerializerSettings
    {
        ReferenceLoopHandling = ReferenceLoopHandling.Ignore
    }));

Here I am trying to save my game data in the bson format but I don't quite know how to turn off the ReferenceLoopHandling in the bson format.

using (var stream = new MemoryStream())
{
    var serializer = new JsonSerializer();
    var writer = new BsonWriter(stream);
    serializer.ReferenceLoopHandling.Equals(false);
    serializer.Serialize(writer, objectToSave);

    File.WriteAllText(path, serializer.ToString());
}

When I run this code I get the following error.

JsonSerializationException: Self referencing loop detected for property 'graph' with type 'StoryGraph'. Path 'nodes[0]'.


Solution

  • You can use the factory methods JsonSerializer.CreateDefault(JsonSerializerSettings) or JsonSerializer.Create(JsonSerializerSettings) to manufacture a serializer with your required settings, then serialize directly to a file using the following extension methods:

    public static partial class BsonExtensions
    {
        // In Json.NET 10.0.1 and later use https://www.nuget.org/packages/Newtonsoft.Json.Bson
        public static void SerializeToFile<T>(T obj, string path, JsonSerializerSettings settings = null)
        {
            using (var stream = new FileStream(path, FileMode.Create))
            using (var writer = new BsonWriter(stream)) // BsonDataWriter in Json.NET 10.0.1 and later
            {
                JsonSerializer.CreateDefault(settings).Serialize(writer, obj);
            }
        }
    
        public static T DeserializeFromFile<T>(string path, JsonSerializerSettings settings = null)
        {
            using (var stream = new FileStream(path, FileMode.Open))
            using (var reader = new BsonReader(stream)) // BsonDataReader in Json.NET 10.0.1 and later
            {
                var serializer = JsonSerializer.CreateDefault(settings);
                //https://www.newtonsoft.com/json/help/html/DeserializeFromBsonCollection.htm
                if (serializer.ContractResolver.ResolveContract(typeof(T)) is JsonArrayContract)
                    reader.ReadRootValueAsArray = true;
                return serializer.Deserialize<T>(reader);
            }
        }
    }
    

    And then serialize as follows:

    BsonExtensions.SerializeToFile(objectToSave, path, 
                                   new JsonSerializerSettings 
                                   { 
                                       ReferenceLoopHandling = ReferenceLoopHandling.Ignore 
                                   });
    

    Notes:

    • Be sure to use the same settings when deserializing.

    • BSON support was moved into its own package Newtonsoft.Json.Bson in Json.NET 10.0.1. In this version or later you should use BsonDataWriter (and BsonDataReader) as BsonWriter has been made obsolete, and will eventually be removed.

    • serializer.ToString() is not going to return the serialized BSON; instead use MemoryStream.ToArray(), i.e.

      File.WriteAllBytes(path, stream.ToArray());
      

      However it's more efficient to stream directly to the file as shown in the extension methods above.

    • serializer.ReferenceLoopHandling.Equals(false); is not the correct way to set the ReferenceLoopHandling property in c#. Instead set it as if it were a field:

       serializer.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
      

      See: Using Properties (C# Programming Guide).

    Demo fiddle here.