Reputation: 95
I'm writing a library to access a web API used to manage data in the backend system. The library will also provide classes with logic to properly manage the data. For this reason most of the classes cannot have public setters for properties or a public constructor that accepts all the properties. Instead I'm trying to use a constructor with internal access modifier that can set all the properties.
When trying to deserialize a JSON string using System.Text.Json it will ignore the internal constructor - it will either use some other constructor or throw an exception. I've also tried annotating the internal constructor with [JsonConstructor]
but it does nothing.
Example class with internal constructor:
public class Entry
{
public int Id { get; }
public string Name { get; set; }
public string Value { get; set; }
[JsonConstructor]
internal Entry(int id, string name, string value)
{
this.Id = id;
this.Name = name;
this.Value = value;
}
}
Suppose the user can use the library to get an Entry
from the backend, modify its name and value, and update it. User should not be able to change the Id
property or create a new Entry
.
Example deserialization:
var json = "{\"id\": 1337, \"name\": \"TestEntry\", \"value\": \"TestValue\"}";
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
var entry = JsonSerializer.Deserialize<Entry>(json, options);
Calling JsonSerializer.Deserialize
will throw System.NotSupportedException
- Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported:
A possible solution is to just make the constructor public and annotate it with [Obsolete("Message", true)]
attribute - when trying to use this constructor the compiler will throw and error, making the constructor unusable.
But I feel this is not the most elegant solution and it also misuses the [Obsolete]
attribute.
Is there any other way of solving this problem?
Upvotes: 4
Views: 4343
Reputation: 790
Had this when I ported an old Azure Function from .Net Core to .Net6.0 and started using the Microsoft.Azure.Functions.Worker
instead of the .core Microsoft.Azure.WebJobs.Extensions.DurableTask
files for the function Orchestrators.
when using Microsoft.DurableTask.Client
Version="1.2.0" i got the same mentioned exception, when trying to de-serialise this passed class\object parameter
System.NotSupportedException - Deserialization of types without a parameterless constructor, a singular parameterized constructor, or a parameterized constructor annotated with 'JsonConstructorAttribute' is not supported:
IN ESSENSE
Microsoft.Extensions.Azure
Version="1.5.0" seems to handle the de-serialisation fine even with unsupported types as per MS: Supported key Types
issue was when passing the detail
object to the OrchestratorActivity possibly trying to de-serialize to FunctionContext
for that method,
var result = await context.CallActivityAsync<string>("EngineRun", detail);
outputs.Add(result);
}
[Function("EngineRun")]
public async Task<string> EngineRun([ActivityTrigger] FunctionContext detail)
This is the detail
object in question:
public class engineRun
{
public string batchid { get; set; }
public string taskid { get; set; }
public string? saleid { get; set; }
public int stage { get; set; }
public ServiceClient? service { get; set; }
}
Hopes this helps - kept me busy for a while.
Edit
to confirm even (braking all the rules) below still works and using System.Text.Json
with Microsoft.Extensions.Azure
:
[JsonPolymorphic]
public class engineRun
{
private bool _started;
[JsonConstructor]
public engineRun() { }
public engineRun(bool started)
{
_started = started;
}
public string batchid { get; set; }
public string taskid { get; set; }
public string? saleid { get; set; }
public int stage { get; set; }
public ServiceClient? service { get; set; }
public bool Started
{
get { return _started; }
private set { value = _started; }
}
}
Upvotes: 1
Reputation: 3303
In this case the best way to achieve this would be by using a separate model for serialization. And then using an internal constructor on the Domain model.
public class EntryDto
{
public int Id { get; }
public string Name { get; }
public string Value { get; }
[JsonConstructor]
public EntryDto(int id, string name, string value)
{
this.Id = id;
this.Name = name;
this.Value = value;
}
public Entry ToDomainModel()
{
return new Entry(this.Id, this.Name, this.Value);
}
}
public class Entry
{
public int Id { get; }
public string Name { get; }
public string Value { get; }
internal Entry(int id, string name, string value)
{
this.Id = id;
this.Name = name;
this.Value = value;
}
}
```
Upvotes: 2