I'm working on a project with SignalR, and I've got some objects that I'm going to be passing along through it. These objects are only explicitly created in my back-end code, and I'd really like to be able to enforce immutability and invariants on them. I'm running into the issue that SignalR requires me (well, really NewtonSoft.Json), to have a default, no-args constructor and public setters on my properties in order for it to serialize and deserialize them over the wire.
Here's a contrived example:
public class Message{
public string Text {get;set;}
public int Awesomeness {get;set;}
}
What I'd like, is something more along these lines (it should probably have readonly private fields and getter-only properties to be fully immutable, but for something that is just a POCO with no methods, good enough)
public class Message {
public string Text {get;private set;}
public int Awesomeness {get;private set;}
public Message( string msg, int awesome){
if (awesome < 1 || awesome > 5){
throw new ArgumentOutOfRangeException("awesome");
}
Text = msg;
Awesomeness = awesome;
}
}
If I do that, though, my object can't get deserialized by the SignalR .NET client library. I can just chuck a default constructor in there, and make my setters public, but then I have to remember to not use them in my code, and make sure no one else on the team uses them without understanding.
I've kicked around the idea of doing this, to mark the default constructor as something that should never be explicitly used:
[Obsolete("Bad! don't do this!")
public Message(){}
But I can't use the Obsolete attribute on just the setter of a property.
If I really wanted to, I could separate out the "real" object from a DTO representation and convert between them, but I'm not really psyched up to write a bunch of boilerplate to do that and introduce another layer.
Is there something I'm overlooking, or do I just need to bite the bullet and deal with it?
If your class does not have a public parameterless constructor, but does have a single public constructor with parameters, Json.NET will call that constructor, matching the constructor arguments to the JSON properties by name using reflection and using default values for missing properties. Matching by name is case-insensitive, unless there are multiple matches that differ only in case, in which case the match becomes case sensitive. Thus if you simply do:
You will be able to serialize and deserialize your class successfully with Json.NET.
Prototype fiddle.
If your class has multiple public constructors, all with parameters, you can mark the one to use with
[JsonConstructor]
, e.g.:See also
JsonSerializerSettings.ConstructorHandling
which tells Json.NET whether to prefer a non-public parameterless constructor over a single public constructor with parameters.