'Equivalent of JObject in System.Text.Json
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?
Solution 1:[1]
In .NET 5 and .NET Core 3.1 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 toJToken
notJObject
. AsJsonElement
is astruct
there is no subclass corresponding to a JSON object. If you want to constrainExtractedData
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 notnull
. So, what is it? It turns out thatdefault(JsonElement)
hasValueKind = JsonValueKind.Undefined
:There is no value (as distinct from Null).
If you attempt to serialize such a default
JsonElement
withJsonSerializer
, an exception will be thrown. I.e. if you simply dovar 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 defineExtractedData
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 asnull
.
See also the related questions:
Solution 2:[2]
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;}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |