Your problem can be reproduced with the following more minimal example. Define the following model:
public class JsonApiMessage
{
public JsonElement data { get; set; }
}
Then attempt to deserialize and re-serialize an empty JSON object like so:
var payload = JsonSerializer.Deserialize<JsonApiMessage>("{}");
var newJson = JsonSerializer.Serialize(payload, new JsonSerializerOptions { WriteIndented = true });
And you will get an exception (demo fiddle #1 here):
System.InvalidOperationException: Operation is not valid due to the current state of the object.
at System.Text.Json.JsonElement.WriteTo(Utf8JsonWriter writer)
at System.Text.Json.Serialization.Converters.JsonConverterJsonElement.Write(Utf8JsonWriter writer, JsonElement value, JsonSerializerOptions options)
The problem seems to be that JsonElement
is a struct
, and the default value for this struct can't be serialized. In fact, simply doing JsonSerializer.Serialize(new JsonElement());
throws the same exception (demo fiddle #2 here). (This contrasts with JObject
which is a reference type whose default value is, of course, null
.)
So, what are your options? You could make all your JsonElement
properties be nullable, and set IgnoreNullValues = true
while re-serializing:
public class JsonApiData
{
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("attributes")]
public JsonElement? Attributes { get; set; }
[JsonPropertyName("meta")]
public JsonElement? Meta { get; set; }
[JsonPropertyName("relationships")]
public JsonElement? Relationships { get; set; }
}
And then:
var reserialisedPayload = JsonSerializer.Serialize(payload, new JsonSerializerOptions { IgnoreNullValues = true });
Demo fiddle #3 here.
Or, in .NET 5 or later, you could mark all of your JsonElement
properties with [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
:
public class JsonApiData
{
// Remainder unchanged
[JsonPropertyName("attributes")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Attributes { get; set; }
[JsonPropertyName("meta")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Meta { get; set; }
[JsonPropertyName("relationships")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement Relationships { get; set; }
}
Doing so will cause uninitialized elements to be skipped during serialization without needing to modify serialization options.
Demo fiddle #4 here.
Or, you could simplify your data model by binding all the JSON properties other than Id
to a JsonExtensionData
property like so:
public class JsonApiData
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement> ExtensionData { get; set; }
}
This approach avoids the need to manually set IgnoreNullValues
when re-serializing, and thus ASP.NET Core will re-serialize the model correctly automatically.
Demo fiddle #5 here.