4

I've been attempting to serialize and deserialize my object in such a way that I am able to specify that certain properties are to be serialized but not deserialized.

Example code as follows:

public interface ISupYo
{
    string Hi { get; }
}
public class SupYo : ISupYo
{
    public string Hi { get; } = "heya";
}

public interface ISup
{
    int hiyo { get; }
}
public class Sup : ISup
{ 
    public Sup(int hiyo)
    {
        this.hiyo = hiyo;
    }

    public int hiyo { get; }
    public ISupYo yo => new SupYo();
}

var myNewSup = JsonConvert.SerializeObject(new Sup(2));
var mySup = JsonConvert.DeserializeObject<Sup>(myNewSup);

If I remove the constructor from class Sup all is well.

But as-is deserialization fails with the following error due to json.net trying to construct the interface ISupYo...

Newtonsoft.Json.JsonSerializationException: 'Could not create an instance of type Scratchpad.Program+ISupYo. Type is an interface or abstract class and cannot be instantiated. Path 'yo.Hi', line 1, position 21.'

I tried out the instructions here Serialize Property, but Do Not Deserialize Property in Json.Net but deserialization fails in the same way.

Using a JsonConverter in this way http://pmichaels.net/tag/type-is-an-interface-or-abstract-class-and-cannot-be-instantiated/ is successful, and so is specifying typeNameHandling and format handling during serialization/deserialization

Why this discrepancy between using/not using the default constructor?

dbc
  • 104,963
  • 20
  • 228
  • 340
  • 2
    What is your serialisation code? What fails exactly? – DavidG Mar 21 '18 at 16:23
  • you need to add parameter-less constructor too in `Sup` – Ehsan Sajjad Mar 21 '18 at 16:23
  • Please provide a [mcve] with the error message. – nvoigt Mar 21 '18 at 16:24
  • @EhsanSajjad that means i can't set the auto property Sup.hiyo if i target the parameterless constructor – Gunnar Már Óttarsson Mar 21 '18 at 16:29
  • @dbc I'm not quite sure i follow, i'm used to having multiple constructors, using the [JsonConstructor] attribute and setting auto properties in this way, which is what happens with the one constructor in this example. I don't understand however why the property yo, of type ISupYo is getting dragged into the mix and then causing the exception (though i know it has something to do with the fact that it was serialized in the first place) – Gunnar Már Óttarsson Mar 21 '18 at 16:34
  • Your actual question is how to exclude the `yo` property from serialization, right? If not, why would you want to serialize `yo`, but not want to deserialize it? (That said, if you really want to serialize but not deserialize yo, use a custom ContractResolver when deserializing, that tells Json.NET to not deserialize the yo property...) –  Mar 21 '18 at 16:35
  • Your error message as nothing to do with your property. [Duplicate](https://stackoverflow.com/questions/5780888/casting-interfaces-for-deserialization-in-json-net). – nvoigt Mar 21 '18 at 16:38
  • @elgonzo no, i do want to serialize and not deserialize, as mentioned i had success with a custom contractresolver and my main question is why this is only needed when i use a non default constructor but works fine when using the default one – Gunnar Már Óttarsson Mar 21 '18 at 23:41
  • @nvoigt updated the question as requested, read your linked duplicate question, thanks, another good solution to my initial problem but still no answer to my actual question. In the question you linked, the asker has get/set properties which json.net supports deserializing. json.net does however not support deserializing to auto properties, see [link](https://github.com/JamesNK/Newtonsoft.Json/issues/703#issuecomment-368796118) and my property isn't even an auto property, it's a property with only a getter and no backing property to hold a value, impossible to set during deserilization – Gunnar Már Óttarsson Mar 21 '18 at 23:41

1 Answers1

9

The cause of the exception you are seeing is an unfortunate interaction of Json.NET functionalities:

  1. If an object being deserialized has a read-only reference type member, Json.NET will populate its value's contents from the JSON stream as long as it is pre-allocated. This is true even if the declared type of the member is abstract or an interface, as the real object returned must obviously be concrete.

    Sample .Net fiddle demonstrating this here.

  2. If an object being deserialized specifies use of a parameterized constructor, Json.NET will read the entire object from the JSON stream, deserialize all properties to their declared types, then match the deserialized properties to constructor arguments by name (modulo case) and construct the object using the matched, deserialized properties. Finally, any unmatched properties will be set back into the object.

  3. Json.NET is a single-pass deserializer that never goes back to re-read previously read JSON tokens.

Sadly, the first two functionalities don't play well together. If all the properties of a parameterized type must be deserialized before the type can be constructed, there's no possibility to populate a pre-allocated, read-only member from the JSON stream, since the stream has already been read.

What's worse, Json.NET seems to try to deserialize JSON properties that do not correspond to a constructor parameter but do correspond to a read-only member, even though it should probably just skip them. Since your ISupYo yo member is an interface, you get the exception you see (unless you have specified TypeNameHandling, in which case you don't). This might be a bug; you could report an issue if you want. The specific problem seems to be that JsonSerializerInternalReader.ResolvePropertyAndCreatorValues() is missing the check to see that non-constructor properties are Writable.

The simplest workaround will require use of a special JsonConverter, as the abovementioned ResolvePropertyAndCreatorValues() does check for the presence of converters. First, introduce SkipDeserializationConverter:

public class SkipDeserializationConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        reader.Skip();
        return existingValue;
    }

    public override bool CanWrite { get { return false; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

And apply it to your type as follows:

[JsonConverter(typeof(SkipDeserializationConverter))]
public ISupYo yo { get { return new SupYo(); } }

The converter simply skips all children of the token currently being read without attempting to deserialize anything. Using it is probably preferable to using TypeNameHandling since the latter can introduce security risks as explained in TypeNameHandling caution in Newtonsoft Json.

Sample working .Net fiddle.

dbc
  • 104,963
  • 20
  • 228
  • 340