Update
Problem Removing the deserialization of a closed object succeeds when the object has a parameterized constructor. # 1038 was open on this subject. It has been fixed in Json.NET release 10.0.1 in changelog 0721bd4 .
Original answer
You found a bug in Json.NET. This only happens when your object is constructed with a parameterized constructor. If I modify my object to have a non-parameterized constructor:
public class CompletionDataRequest { public CompletionDataRequest(string text, int cursorPosition, string dataSource, string project) { Text = text; CursorPosition = cursorPosition; DataSource = dataSource; Project = project; } [JsonConstructor] private CompletionDataRequest() { } [JsonProperty] public string Text { get; private set; } [JsonProperty] public int CursorPosition { get; private set; } [JsonProperty] public string DataSource { get; private set; } [JsonProperty] public string Project { get; private set; } }
Then Json.NET correctly throws a JsonSerializationException .
The cause of the error is as follows. When creating an object with a constructor without parameters, Json.NET first creates the object and then populates it with JsonSerializerInternalReader.PopulateObject() . This method has the following (simplified) logic:
private object PopulateObject(object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, string id) { bool finished = false; do { switch (reader.TokenType) { case JsonToken.PropertyName: { // Read and process the property. } case JsonToken.EndObject: finished = true; break; case JsonToken.Comment: // ignore break; default: throw JsonSerializationException.Create(reader, "Unexpected token when deserializing object: " + reader.TokenType); } } while (!finished && reader.Read()); if (!finished) { ThrowUnexpectedEndException(reader, contract, newObject, "Unexpected end when deserializing object."); } return newObject; }
As you can see, there is if (!finished) logic to verify that the object is actually closed.
However, when creating an object with a parameterized constructor, the properties are read before building the object using JsonSerializerInternalReader.ResolvePropertyAndCreatorValues() :
private List<CreatorPropertyContext> ResolvePropertyAndCreatorValues(JsonObjectContract contract, JsonProperty containerProperty, JsonReader reader, Type objectType) { List<CreatorPropertyContext> propertyValues = new List<CreatorPropertyContext>(); bool exit = false; do { switch (reader.TokenType) { case JsonToken.PropertyName: // Read and process the property. break; case JsonToken.Comment: break; case JsonToken.EndObject: exit = true; break; default: throw JsonSerializationException.Create(reader, "Unexpected token when deserializing object: " + reader.TokenType); } } while (!exit && reader.Read()); return propertyValues; }
As you can see, there is no equivalent check for exit being true.
Problem Removing the deserialization of a closed object succeeds when the object has a parameterized constructor. # 1038 for this.