Using C# 9 on .NET 5.0, I have a bunch of record types, like this:
public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened)
{
public SomethingHappenedEvent(object theThing, string whatHappened)
: this(Guid.NewGuid(), theThing, whatHappened)
{ }
}
As you might expect, they get serialized and sent elsewhere for handling. Senders call the two-argument constructor and get a new Id, but the deserializer needs to use the "primary" 3-argument constructor implied by the record declaration. I'm using Newtonsoft Json.NET and I sure wish this worked:
var record = new SomethingHappenedEvent("roof", "caught fire");
var json = JsonConvert.SerializeObject(record);
var otherSideRecord = JsonConvert.DeserializeObject<SomethingHappenedEvent>(json);
Assert.AreEqual(record, otherSideRecord);
Of course it doesn't. It throws JsonSerializationException. It can't find the right constructor because there are two, neither is a default zero-argument constructor, and neither is marked with JsonConstructorAttribute. My question is really "What options do I have for getting something similar?". This would be great:
[JsonConstructor]
public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened)
{
public SomethingHappenedEvent(object theThing, string whatHappened)
: this(Guid.NewGuid(), theThing, whatHappened)
{ }
}
But that tries to apply the attribute to the type, which is invalid. And this is a syntax error in C#, though apparently it works in F#.
public record SomethingHappenedEvent
[JsonConstructor]
(Guid Id, object TheThing, string WhatHappened)
{
public SomethingHappenedEvent(object theThing, string whatHappened)
: this(Guid.NewGuid(), theThing, whatHappened)
{ }
}
My current solution is to leave these types as plain classes and live with all the extra boilerplate. I'm also aware I can omit the custom constructor and make my callers generate the ids. This works because there's only one constructor for json.net to find. It's certainly terse! But I don't love repeating code at all the call sites, even if it is small in this case.
public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened) { }
FWIW it sounds like System.Text.Json has the same limitation.
NET objects (deserialize) A common way to deserialize JSON is to first create a class with properties and fields that represent one or more of the JSON properties. Then, to deserialize from a string or a file, call the JsonSerializer. Deserialize method.
In Deserialization, it does the opposite of Serialization which means it converts JSON string to custom . Net object. In the following code, it creates a JavaScriptSerializer instance and calls Deserialize() by passing JSON data. It returns a custom object (BlogSites) from JSON data.
Parse() method is an object class method and this method is used to parse the JSON string into the objects of C#. Based on the key value it parses the data of string and then it retrieves the data by using the key values.
JSON.Net is well supported and it appears that Microsoft intend to adopt it themselves "We on the web team will be including JSON.NET as the default JSON Serializer in Web API when it releases, so that'll be nice." from hanselman.com/blog/… Just be aware of the embedded library for JSon serializing's performance in .
Firstly, you only have to do this when you create your own constructors. This is due to the fact that on instantiation it won't know which one to use.
Secondly, note that (by default) the deserializer will use the property and constructor names and overwrite the ones you omit in the actual constructor type. Furthermore, each parameter in the constructor must bind to an object property or field on deserialization. The formers can lead to subtle errors if you are not aware of them, however this is not limited solely to records.
All that aside, you had the attribute in the wrong place. In short, the attribute needs to be on the constructor.
Wildly contrived nonsensical example:
Given
public record TestRecord(Guid Id)
{
[JsonConstructor]
public TestRecord(object theThing, string whatHappened) : this(Guid.NewGuid())
{
}
}
Test
var record = new TestRecord(Guid.NewGuid());
var json = JsonConvert.SerializeObject(record,Formatting.Indented);
Console.WriteLine(json);
var otherSideRecord = JsonConvert.DeserializeObject<TestRecord>(json);
// note this paradoxically still works, because it has overwritten the ID
Console.WriteLine(record == otherSideRecord);
Ouput
{
"Id": "2905cfaf-d13d-4df1-af83-e4dcde20d44f"
}
True
Note that the attribute also works with Text.Json
var json = JsonSerializer.Serialize(record);
var otherSideRecord = JsonSerializer.Deserialize<TestRecord>(json);
I ran across another option while experimenting. I'll leave it here in case it's useful to someone. You can convert the custom constructor(s) to factory method(s). This leaves only one constructor for the deserializer to find.
public record SomethingHappenedEvent(Guid Id, object TheThing, string WhatHappened)
{
public static SomethingHappenedEvent Create(object theThing, string whatHappened)
=> new(Guid.NewGuid(), theThing, whatHappened);
}
It changes the syntax at the call sites. Just call Create instead of new:
var myEvent = SomethingHappenedEvent.Create("partyPeople", "gotDown");
Of course you could push the static factory method(s) to a separate factory object - that might be useful if your object construction has richer dependencies.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With