I have DTO class that has a property of type JObject
. This DTO class is send/receive over HTTP between multiple services. JObject is used because the ExtractedData
does not have predefined properties
public class MyDTO
{
public JObject ExtractedData {get;set;}
}
I am converting this project to .NET 5. What is equivalent to JObject in .NET 5? I am trying to avoid JsonDocument because (from the docs):
JsonDocument builds an in-memory view of the data into a pooled buffer. Therefore, unlike JObject or JArray from Newtonsoft.Json, the JsonDocument type implements IDisposable and needs to be used inside a using block.
I am planing to use JsonElement
. Is this the most appropriate choice or is there any other type available to hold JSON as an object?
The four new types are JsonArray , JsonObject , JsonNode and JsonValue . The closest type to JObject is JsonObject which offers similar functionality.
Json does case-insensitive property name matching by default. The System. Text. Json default is case-sensitive, which gives better performance since it's doing an exact match.
JObject. It represents a JSON Object. It helps to parse JSON data and apply querying (LINQ) to filter out required data. It is presented in Newtonsoft.
The simplest way to get a value from LINQ to JSON is to use the Item[Object] index on JObject/JArray and then cast the returned JValue to the type you want. JObject/JArray can also be queried using LINQ.
The closest equivalent to JObject
is indeed JsonElement
so you could modify your DTO as follows:
public class MyDTO
{
public JsonElement ExtractedData {get;set;}
}
There is no need to worry about disposing of any documents as, internally, the JsonElementConverter
used by JsonSerializer
returns a non-pooled element (by cloning the element in .NET 5).
However, the correspondence is not exact, so keep the following in mind:
JsonElement
represents any JSON value and thus corresponds most closely to JToken
not JObject
. As JsonElement
is a struct
there is no subclass corresponding to a JSON object. If you want to constrain ExtractedData
to be a JSON object you will need to check this in the setter:
public class MyDTO
{
JsonElement extractedData;
public JsonElement ExtractedData
{
get => extractedData;
set
{
if (value.ValueKind != JsonValueKind.Object
// && value.ValueKind != JsonValueKind.Null Uncomment if you want to allow null
)
throw new ArgumentException(string.Format("{0} is not a JSON object type", value.ValueKind));
extractedData = value;
}
}
}
Since JsonElement
is a struct, the default value is not null
. So, what is it? It turns out that default(JsonElement)
has ValueKind = JsonValueKind.Undefined
:
There is no value (as distinct from Null).
If you attempt to serialize such a default JsonElement
with JsonSerializer
, an exception will be thrown. I.e. if you simply do
var json = JsonSerializer.Serialize(new MyDTO());
Then a System.InvalidOperationException: Operation is not valid due to the current state of the object.
exception is thrown.
You have a few options to avoid this problem:
In .NET 5 you can apply [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
like so:
public class MyDTO
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement ExtractedData {get;set;}
}
This causes uninitialized values of ExtractedData
to be skipped during serialization.
In .NET Core 3.x JsonIgnoreCondition
does not exist, so you could instead define ExtractedData
to be nullable:
public class MyDTO
{
public JsonElement? ExtractedData {get;set;}
}
Or you could initialize it to a null JsonElement
like so:
public class MyDTO
{
public JsonElement ExtractedData {get;set;} = JsonExtensions.Null;
}
public static class JsonExtensions
{
static readonly JsonElement nullElement = CreateNull();
public static JsonElement Null => nullElement;
static JsonElement CreateNull()
{
using var doc = JsonDocument.Parse("null");
return doc.RootElement.Clone();
}
}
Both options cause uninitialized values of ExtractedData
to serialize as null
.
See also the related questions:
As of Nov 2021, .NET 6
introduces the System.Text.Json.Nodes namespace which:
Provides types for handling an in-memory writeable document object model (DOM) for random access of the JSON elements within a structured view of the data
The four new types are JsonArray
, JsonObject
, JsonNode
and JsonValue
.
The closest type to JObject
is JsonObject
which offers similar functionality.
See below for some examples:
// create object manually using initializer syntax
JsonObject obj = new JsonObject
{
["Id"] = 3,
["Name"] = "Bob",
["DOB"] = new DateTime(2001, 02, 03),
["Friends"] = new JsonArray
{
new JsonObject
{
["Id"] = 2,
["Name"] = "Smith"
},
new JsonObject
{
["Id"] = 4,
["Name"] = "Jones"
}
}
};
// random access to values
int id = (int)obj["Id"];
DateTime dob = (DateTime)obj["DOB"];
string firstFriendName = (string)obj["Friends"][0]["Name"];
Some other cool things which now make using System.Text.Json
much easier in .NET6
are listed below.
Parse, Create, and DOM Manipulation
// parse
var jsonObj = JsonNode.Parse(jsonString).AsObject();
If you have a JsonElement
(perhaps after deserializing into dynamic
, object
, or JsonElement
) you can call Create
, now you have a navigable and writable DOM object:
// create
JsonObject obj = JsonObject.Create(jsonElement);
You can Add/Remove properties:
obj.Add("FullName", "Bob Smith");
bool successfullyRemoved = obj.Remove("Name");
Safely interrogate object for particular key using ContainsKey
and TryGetPropertyValue
(which returns a JsonNode
):
if (obj.ContainsKey("Hobbies"))
// do stuff
if (obj.TryGetPropertyValue("Hobbies", out JsonNode? node))
// do stuff with node
Project and Filter data
It's possible to use Linq to project and filter the JsonObject
:
// select Keys
List<string> keys = obj.Select(node => node.Key).ToList();
// filter friends
var friends = obj["Friends"].AsArray()
.Where(n => (int)n.AsObject()["Id"] > 2);
Deserialize Json
It's now easy to deserialize the Json or deserialize a portion of the Json. This is useful when we only want to deserialize partial Json from the main object. For the example above we can deserialize the list of friends into a generic List<Friend>
easily:
List<Friend> friends = obj["Friends"].AsArray().Deserialize<List<Friend>>();
where Deserilize<T>()
is an extension method on JsonNode
.
Serialize
It's easy to serialize the JsonObject
by using ToJsonString()
:
string s = obj.ToJsonString();
// write pretty json with WriteIndented
string s = obj.ToJsonString(new JsonSerializerOptions { WriteIndented = true }));
In your particular case you could define JsonObject
in your DTO:
public class MyDTO
{
public JsonObject ExtractedData {get;set;}
}
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