2

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]'.

Andrew Pullins
  • 496
  • 1
  • 14
  • 24

2 Answers2

2

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.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • This almost works perfectly. I am able to serialize to Bson but unable to deserialize. I am getting this error: – Andrew Pullins Oct 31 '19 at 18:05
  • JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[Item]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object. – Andrew Pullins Oct 31 '19 at 18:05
  • 1
    @AndrewPullins - that error arises when you try to deserialize a JSON object into a collection, see [Cannot deserialize the current JSON object (e.g. {“name”:“value”}) into type 'System.Collections.Generic.List`1](https://stackoverflow.com/q/21358493). Or if you are trying to deserialize a collection as the root object from BSON, see [Bson array (de)serialization with Json.NET](https://stackoverflow.com/q/16910369). But we would need to see a [mcve] to say for sure, and your question doesn't include one. – dbc Oct 31 '19 at 18:08
  • 1
    @AndrewPullins - does setting `ReadRootValueAsArray` as shown in https://dotnetfiddle.net/KzDbJ1 fix your problem? – dbc Oct 31 '19 at 18:12
  • Thanks @dbc the ReadRootValueAsArray made it work! This is working amazingly. Thank you so much. – Andrew Pullins Oct 31 '19 at 18:22
1

You can also directly set the serializer:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
    // Fix: Ignore loops
    serializer.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
    ...

This solved the issue for me in the Unity C# context.

schlenger
  • 1,447
  • 1
  • 19
  • 40