0

I'm trying to deserialize an object in an extension class, and it's not working like I would expect. I've read that I can use JsonSerializerSettings to let the JSON deserializer use a private constructor. But for some reason, JsonSerializerSettings aren't working for me. Here's the JSON reader/writer I'm using:

public class FileStorageService<T> : IStorageService<T> where T : IEquatable<T>

    /// <summary>
    /// Writes the given object instance to a JSON file.
    /// Source: https://stackoverflow.com/questions/6201529/turn-c-sharp-object-into-a-json-string-in-net-4
    /// </summary>
    /// <param name="filePath">The file path to write the object instance to.</param>
    /// <param name="objectToWrite">The object instance to write to the file.</param>
    /// 
    /// Comment source: https://stackoverflow.com/questions/6115721/how-to-save-restore-serializable-object-to-from-file
    public void WriteJSONFile(string filePath, T objectToWrite)
    {
        string json = JsonConvert.SerializeObject(objectToWrite);
        File.WriteAllText(filePath, json);
    }

    /// <summary>
    /// Reads an object instance from a JSON file.
    /// </summary>
    /// <param name="filePath">The file path to read the object instance from.</param>
    /// <returns>Returns a new instance of the object read from the JSON file.</returns>
    /// 
    /// Comment source: https://stackoverflow.com/questions/6115721/how-to-save-restore-serializable-object-to-from-file
    public T ReadJSONFile(string filePath)
    {
        if (!File.Exists(filePath))
        {
            FileStream fs = new FileStream(filePath, FileMode.CreateNew);
            fs.Close();
        }
        JsonSerializerSettings settings = new JsonSerializerSettings
        {
            ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
        };
        string json = File.ReadAllText(filePath, Encoding.UTF8);
        return JsonConvert.DeserializeObject<T>(json, settings);
    }
}

And here's an example of a class I'm trying to read/write:

public class MyObject : IEquatable<MyObject>
{
    public string myString { get; set; }
    public byte[] myBytes { get; set; }

    protected MyHasher hasher;

    public MyObject(string first, string second)
    {
        this.myString = first;

        hasher = new MyHasher();
        this.myBytes = hasher.ComputeHash(second);
    }

    ... (implementing IEquatable below)
}

When I run the program in Visual Studio, I get a null pointer exception:

"An unhandled exception of type 'System.ArgumentNullException' occurred in Newtonsoft.Json.dll

Additional information: String reference not set to an instance of a String."

...pointing to the hasher.ComputeHash(second) method, with the call stack:

> this.myBytes = hasher.ComputeHash(second);
> return JsonConvert.DeserializeObject<T>(json, settings);

Debugging it, I found that JsonConvert.DeserializeObject is calling MyObject's public constructor and giving it null values (sub-question: how is this possible?), which I don't want. I want it to use the default constructor instead.

I could add the : new()constraint to force objects to have a public parameterless constructor, but I want my extension class to be able to handle any type of object (strings, ints, etc.), not just custom objects I create.

As a final complication, note that I can't change MyObject in any way.

How can I do this?

dbc
  • 104,963
  • 20
  • 228
  • 340
Miryafa
  • 163
  • 1
  • 2
  • 15
  • 2
    But you class does not have a default constructor, only one that accepts two parameters. – Ric Aug 10 '17 at 15:54
  • @Ric I know that. Does it matter? If so, why? – Miryafa Aug 10 '17 at 15:55
  • 1
    Maybe this will help some more: https://stackoverflow.com/questions/23017716/json-net-how-to-deserialize-without-using-the-default-constructor – Ric Aug 10 '17 at 15:56
  • @Ric I think the answer you linked won't work for the same reason I don't want to use `: new()`, and also because I'm not sure how I would assign properties to an unknown class. – Miryafa Aug 10 '17 at 16:17
  • You're using `AllowNonPublicDefaultConstructor` so why not just add a private constructor? Json.NET will use it in preference to the parameterized constructor. – dbc Aug 10 '17 at 19:26
  • @dbc I want my extension class to work with any type of object. – Miryafa Aug 10 '17 at 20:09
  • Sorry, to put it more plainly, I can't change MyObject. – Miryafa Aug 10 '17 at 20:17
  • Then you can't deserialize it. Create mirror objects intended for the deserialization and then write the mapping between these objects and the real ones. – Lasse V. Karlsen Aug 10 '17 at 20:19
  • 2
    Secondly, if you know that your type doesn't have a default constructor (as per a comment), but you want deserialization to use the default constructor ... then what do you really want? You want Json.net to use a non-existent constructor? – Lasse V. Karlsen Aug 10 '17 at 20:21
  • @LasseV.Karlsen Your first point doesn't make sense to me. Strings and other literals can be deserialized, even though they don't (to my knowledge) have a default constructor. At least, they don't work with the constraint `: new()`. So somehow JSON.NET is accounting for the difference. I want my code to be able to do that too. – Miryafa Aug 10 '17 at 21:26
  • Your second point doesn't make sense to me either. Every object has (as far as I know) a public default constructor, even/particularly if you don't write it in the code. How can you say there's no default constructor? – Miryafa Aug 10 '17 at 21:28
  • 2
    @Miryafa - that is wrong. For reference types, a default constructor is only auto-generated if the class has no constructors at all. Once there is at least one constructor, a constructor is no longer auto-generated. See [this doc page](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/instance-constructors) and [why is there not always a default constructor](https://stackoverflow.com/q/16137767) for more info. – dbc Aug 10 '17 at 21:41

0 Answers0