JSON.net: how to deserialize without using the default constructor?
Json.Net prefers to use the default (parameterless) constructor on an object if there is one. If there are multiple constructors and you want Json.Net to use a non-default one, then you can add the [JsonConstructor]
attribute to the constructor that you want Json.Net to call.
[JsonConstructor]
public Result(int? code, string format, Dictionary<string, string> details = null)
{
...
}
It is important that the constructor parameter names match the corresponding property names of the JSON object (ignoring case) for this to work correctly. You do not necessarily have to have a constructor parameter for every property of the object, however. For those JSON object properties that are not covered by the constructor parameters, Json.Net will try to use the public property accessors (or properties/fields marked with [JsonProperty]
) to populate the object after constructing it.
If you do not want to add attributes to your class or don't otherwise control the source code for the class you are trying to deserialize, then another alternative is to create a custom JsonConverter to instantiate and populate your object. For example:
class ResultConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Result));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Load the JSON for the Result into a JObject
JObject jo = JObject.Load(reader);
// Read the properties which will be used as constructor parameters
int? code = (int?)jo["Code"];
string format = (string)jo["Format"];
// Construct the Result object using the non-default constructor
Result result = new Result(code, format);
// (If anything else needs to be populated on the result object, do that here)
// Return the result
return result;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then, add the converter to your serializer settings, and use the settings when you deserialize:
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new ResultConverter());
Result result = JsonConvert.DeserializeObject<Result>(jsontext, settings);
Deserialize json with missing default constructor in the class in C# using JSON.NET
You can work around the issue by making a custom JsonConverter
for the ConnectionSummary
class like this:
public class ConnectionSummaryConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ConnectionSummary);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
Connection conn = new Connection
{
Id = (string)jo["connectionId"],
SystemId = (string)jo["systemId"]
};
return new ConnectionSummary(conn);
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then deserialize like this:
var deserial = JsonConvert.DeserializeObject<ConnectionSummary>(json, new ConnectionSummaryConverter());
Fiddle: https://dotnetfiddle.net/U4UR3o
Usage of non-default constructor breaks order of deserialization in Json.net
You need to use the same settings for deserialization as you did for serialization. That being said, you appear to have encountered a bug or limitation in Json.NET.
It is happening for the following reason. If your Middle
type does not have a public parameterless constructor, but does have a single public constructor with parameters, JsonSerializerInternalReader.CreateObjectUsingCreatorWithParameters()
will call that constructor, matching the constructor arguments to the JSON properties by name and using default values for missing properties. Then afterwards any remaining unused JSON properties will be set into the type. This enables deserialization of read-only properties. E.g. if I add a read-only property Foo
to your Middle
class:
public class Middle
{
readonly int foo;
public int Foo { get { return foo; } }
public Middle(int Foo) { this.foo = Foo; "Middle".Dump(); }
public Root Root { get; set; }
public Child Child { get; set; }
}
The value of Foo
will be successfully deserialized. (The matching of JSON property names to constructor argument names is shown here in the documentation, but not well explained.)
However, it appears this functionality interferes with PreserveReferencesHandling.All
. Since CreateObjectUsingCreatorWithParameters()
fully deserializes all child objects of the object being constructed in order to pass those necessary into its constructor, if a child object has a "$ref"
to it, that reference will not be resolved, since the object will not have been constructed yet.
As a workaround, you could add a private constructor to your Middle
type and set ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor
:
public class Middle
{
private Middle() { "Middle".Dump(); }
public Middle(int Foo) { "Middle".Dump(); }
public Root Root { get; set; }
public Child Child { get; set; }
}
And then:
var settings = new JsonSerializerSettings
{
Formatting = Newtonsoft.Json.Formatting.Indented,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
PreserveReferencesHandling = PreserveReferencesHandling.All,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
};
var deserialized = JsonConvert.DeserializeObject<Root>(json, settings);
Of course, if you do this, you loose the ability to deserialize read-only properties of Middle
, if there are any.
You might want to report an issue about this. In theory, at the expense of higher memory usage, when deserializing a type with a parameterized constructor, Json.NET could:
- Load all child JSON properties into an intermediate
JToken
. - Only deserialize those required as constructor arguments.
- Construct the object.
- Add the object to the
JsonSerializer.ReferenceResolver
. - Deserialize and set the remaining properties.
However, if any of the constructor arguments thenselves have a "$ref"
to the object being deserialized, this doesn't appear easily fixable.
How to deserialize class without calling a constructor?
You could create a class that inherits from
CustomCreationConverter
and useFormatterServices.GetSafeUninitializedObject
to create your
object. It skips calling the constructor.More about CustomCreationConverter here.
Placing
[JsonObject(MemberSerialization.Fields)]
on a class will make Json.NET useFormatterServices.GetSafeUninitializedObject
by default (although
Fields mode will also serialize public/private fields rather than
public properties which you may not want).Move the logic you don't want run outside of the default constructor.
Why does JsonConvert treat default constructors and parameterized constructors differently?
How does Json.NET deserialize an object differently when it has a parameterized constructor vs. when it has a default constructor?
Json.NET is a streaming deserializer. Whenever possible it deserializes as it streams through the JSON rather than preloading the complete JSON into an intermediate representation before final deserialization.
Thus, when deserializing a JSON object with a default constructor, it first constructs the corresponding .Net object. It then recursively populates the object's .Net members by streaming through the key/value pairs in the JSON until the end of the JSON object. For each pair encountered, it finds the corresponding .Net member. If the value is a primitive type, it deserializes the primitive and sets the value. But if the value is a complex type (JSON object or array) it constructs the child object if necessary, sets the value back in the parent, and then populates it recursively as it continues to stream.
However, when deserializing an object with a parameterized constructor, Json.NET cannot use this streaming algorithm and instead must first fully deserialize the JSON object to an intermediate table of deserialized .Net name/value pairs, matching each JSON value to its corresponding .Net constructor argument or property by name and then deserializing to the type declared in .Net. Only then can the object be constructed by passing the deserialized constructor parameters into the constructor, and setting the remainder as property values.
For details on this process, see
- JSON.net: how to deserialize without using the default constructor?
- How does JSON deserialization in C# work.
(There is a third algorithm for ISerializable
objects which does not apply in your case.)
Why is my surrogate public List<KeyValuePair<Scenario, float>> SerializedDict
property not deserialized correctly when deserializing via a default constructor?
The reason is explained in this answer to Why are all the collections in my POCO are null when deserializing some valid json with the .NET Newtonsoft.Json component, and arises about of the specifics of Json.NET's Populate()
algorithm:
It calls the getter in the parent class to get the current value of the property being deserialized.
If null, and unless a custom constructor is being used, it allocates an instance of the property's returned type (using the
JsonContract.DefaultCreator
method for the type).It calls the setter in the parent to set the allocated instance back into the parent.
It proceeds to populate the instance of the type.
It does not set the instance back a second time, after it has been populated.
Thus the setter for SerializedDict
is not called after the list is populated.
But when the parent class has a parameterized constructor, the property value SerializedDict
is fully deserialized before its parent is constructed, so the setter is called with a fully populated surrogate list.
How can I create a surrogate collection property that works in both scenarios?
You can use an array instead of a list. Since an array cannot be resized, it must be fully deserialized and populated before it can be set back in the parent object:
public class ReferenceTesting
{
public KeyValuePair<Scenario, float> [] SerializedDict
{
get { return _Dict.ToArray(); }
set { _Dict = value.ToDictionary(x => x.Key, x => x.Value); }
}
// Remainder unchanged
You could make the array property be private
if you want, by marking it with [JsonProperty]
.
By the way, your current deserialization creates duplicate Scenario
objects in the scenarios
and _Dict
collections as shown by demo fiddle #1 here.
One way to fix this would be to serialize just _Dict
(assuming that all scenarios are in the dictionary). Another would be to use PreserveReferencesHandling
, e.g. by adding [JsonObject(IsReference = true)]
to Scenario
:
[JsonObject(IsReference = true)]
public class Scenario
{
// Remainder unchanged
}
Notes:
There is no standard for serialization of references in JSON. Json.NET's implementation may not match that of other serializers.
PreserveReferencesHandling
doesn't work for objects with parameterized constructors (see here for details), so make sureScenario
doesn't have one.
Demo fiddle #2 here showing everything working correctly with a default constructor, and #3 here with a parameterized constructor.
How to set up constructors for deserialization of Get only properties without having to duplicate code in c#?
You could make the id
argument nullable:
abstract class Box
{
public Box(double panelThickness) : this(null, panelThickness) { }
protected Box(int? id, double panelThickness)
{
ID = id ?? IDGenerator.GetNewID();
PanelThickness = panelThickness;
}
public int ID { get; }
public double PanelThickness { get; }
}
class RectangularBox : Box
{
private static double _rectPanelThickness = 0.2;
public RectangularBox(double xDimension, double yDimension)
: this(null, xDimension, yDimension) { }
[JsonConstructor]
private RectangularBox(int? id, double xDimension, double yDimension)
: base(id, _rectPanelThickness)
{
XDimension = xDimension;
YDimension = yDimension;
}
public double XDimension { get; }
public double YDimension { get; }
}
Obviously if your JSON does not contain an id
then a new id will be generated, but that seems like something within your control.
Can I deserialize Json with private constructor using System.Text.Json?
It would appear the answer is "No," or at least, "Not Yet".
This is a known limitation of the System.Text.Json serializer for [System.Text.Json] v1. We plan to support this in the future.
-ashonkhanYou can write a custom converter for this...For the [ASP.NET Core] 3.0 release, there is no planned additional support for calling a non-default constructor during deserialization. That would have to be done by a custom converter. -steveharter
The custom converter option linked would allow you to use whatever API you do have to build the object, but isn't the same as what, say, Newtonsoft.Json or Entity Framework can do by fiddling with reflection and private constructors, so probably not what you were looking for.
Related Topics
When Do You Use the "This" Keyword
Is Using Random and Orderby a Good Shuffle Algorithm
How to Get All Child Controls of a Windows Forms Form of a Specific Type (Button/Textbox)
Best Way to Randomize an Array With .Net
Type Checking: Typeof, Gettype, or Is
How to Remove All Event Handlers from an Event
Should 'Using' Directives Be Inside or Outside the Namespace
Nesting Await in Parallel.Foreach
How to Convert a Column Number (E.G. 127) into an Excel Column (E.G. Aa)
How to Recursively List All the Files in a Directory in C#
Getting Attributes of Enum'S Value
How to Copy the Contents of One Stream to Another