Select part of json with System.text.json
You can emulate the Json.NET logic as follows:
using var jsonParse = JsonDocument.Parse(json); // Be sure to dispose the JsonDocument!
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var products = jsonParse.RootElement
.GetProperty("searchedProducts") // Get the searchedProducts value
.GetProperty("productDetails") // Get the productDetails value
.EnumerateArray() // Enumerate its items
.Where(n => n.GetProperty("isFounderEdition").GetBoolean()) // Filter on those for which isFounderEdition == true
.Select(n => n.ToObject<CarteGraphique>(options)) // Deserialize to a CarteGraphique
.ToList();
// Add the searchedProducts.featuredProduct item to the list.
var featuredProduct = jsonParse.RootElement
.GetProperty("searchedProducts")
.GetProperty("featuredProduct")
.ToObject<CarteGraphique>(options);
products.Add(featuredProduct);
Where ToObject<T>(this JsonElement element, JsonSerializerOptions options = null)
is an extension method from this answer to System.Text.Json.JsonElement ToObject workaround:
public static partial class JsonExtensions
{
public static T ToObject<T>(this JsonElement element, JsonSerializerOptions options = null)
{
var bufferWriter = new System.Buffers.ArrayBufferWriter<byte>();
using (var writer = new Utf8JsonWriter(bufferWriter))
element.WriteTo(writer);
return JsonSerializer.Deserialize<T>(bufferWriter.WrittenSpan, options);
}
public static T ToObject<T>(this JsonDocument document, JsonSerializerOptions options = null)
{
if (document == null)
throw new ArgumentNullException(nameof(document));
return document.RootElement.ToObject<T>(options);
}
}
Notes:
JsonDocument
is disposable. Failure to dispose of it will result in the memory not being returned to the pool, which will increase GC impact across various parts of the framework, according to the docs.In .NET 6.0 Microsoft plans to implement direct deserialization from a
JsonElement
, see We should be able serialize and deserialize from DOM #31274. In the meantime the above extension method can be used.I did not add any checking for missing properties. If properties might be missing, use
JsonElement.TryGetProperty()
. See Getting nested properties with System.Text.Json for some extension methods that can make that easier.Unlike Json.NET, System.Text.Json is case-sensitive by default. To enable case-insensitive deserialization use
JsonSerializerOptions { PropertyNameCaseInsensitive = true }
as shown in JsonSerializer.Deserialize fails.
Demo fiddle here.
Convert object to System.Text.Json.JsonElement
In .NET 6 methods are being added to JsonSerializer
to serialize an object directly to a JsonElement
or JsonDocument
:
public static partial class JsonSerializer
{
public static JsonDocument SerializeToDocument<TValue>(TValue value, JsonSerializerOptions? options = null);
public static JsonDocument SerializeToDocument(object? value, Type inputType, JsonSerializerOptions? options = null);
public static JsonDocument SerializeToDocument<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo);
public static JsonDocument SerializeToDocument(object? value, Type inputType, JsonSerializerContext context);
public static JsonElement SerializeToElement<TValue>(TValue value, JsonSerializerOptions? options = null);
public static JsonElement SerializeToElement(object? value, Type inputType, JsonSerializerOptions? options = null);
public static JsonElement SerializeToElement<TValue>(TValue value, JsonTypeInfo<TValue> jsonTypeInfo);
public static JsonElement SerializeToElement(object? value, Type inputType, JsonSerializerContext context);
}
Thus in .NET 6 you will be able to do:
using var jsonDocument = JsonSerializer.SerializeToDocument(new MyClass { Data = "value" });
or
var jsonElement = JsonSerializer.SerializeToElement(new MyClass { Data = "value" });
Notes:
JsonSerializerContext
andJsonTypeInfo<T>
are newly exposed in .NET 6 and provide metadata about a set of types, or a single typeT
, that is relevant to JSON serialization. They are used when serializing using metadata and code generated at compile time. See Try the new System.Text.Json source generator for details.JsonDocument
isIDisposable
, and in fact must needs be disposed because, according to the docs:JsonDocument
builds an in-memory view of the data into a pooled buffer. Therefore, unlikeJObject
orJArray
from Newtonsoft.Json, theJsonDocument
type implementsIDisposable
and needs to be used inside ausing
block.In your sample code you do not dispose of the document returned by
JsonDocument.Parse()
, but you should.The new methods should be present in .NET 6 RC1.
In .NET 5 and earlier a method equivalent to JObject.FromObject()
is not currently available out of the box in System.Text.Json
. There is an open enhancement about this, currently targeted for Future:
- We should be able serialize and serialize from DOM #31274.
In the interim you may get better performance by serializing to an intermediate byte
array rather than to a string, since both JsonDocument
and Utf8JsonReader
work directly with byte
spans rather than strings or char
spans, like so:
public static partial class JsonExtensions
{
public static JsonDocument JsonDocumentFromObject<TValue>(TValue value, JsonSerializerOptions options = default)
=> JsonDocumentFromObject(value, typeof(TValue), options);
public static JsonDocument JsonDocumentFromObject(object value, Type type, JsonSerializerOptions options = default)
{
var bytes = JsonSerializer.SerializeToUtf8Bytes(value, type, options);
return JsonDocument.Parse(bytes);
}
public static JsonElement JsonElementFromObject<TValue>(TValue value, JsonSerializerOptions options = default)
=> JsonElementFromObject(value, typeof(TValue), options);
public static JsonElement JsonElementFromObject(object value, Type type, JsonSerializerOptions options = default)
{
using var doc = JsonDocumentFromObject(value, type, options);
return doc.RootElement.Clone();
}
}
And then call it like:
using var doc = JsonExtensions.JsonDocumentFromObject(new MyClass { Data = "value" });
Or, if you need to use the root element outside the scope of a using
statement:
var element = JsonExtensions.JsonElementFromObject(new MyClass { Data = "value" });
Notes:
As noted above, a
JsonDocument
needs to be disposed after being created.
The aboveJsonExtensions.JsonElementFromObject()
extension methods correctly dispose of their internal document and returns a clone of the root element, as recommended in the documentation.Serializing to an intermediate Utf8 byte sequence is likely to be more performant than serializing to a
string
because, according to the docs:Serializing to UTF-8 is about 5-10% faster than using the string-based methods. The difference is because the bytes (as UTF-8) don't need to be converted to strings (UTF-16).
For the inverse method, see System.Text.Json.JsonElement ToObject workaround.
Demo fiddle here.
Select type to use for deserialization in System.Text.Json
Thanks to @dbc for pointing me in the right direction, here's the converter I've ended up with:
public class JobBaseConverter : JsonConverter<JobBase>
{
public override JobBase Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
using (var jsonDocument = JsonDocument.ParseValue(ref reader))
{
if (jsonDocument.RootElement.TryGetProperty(nameof(ScheduledJob.Schedule), out var typeProperty))
{
return JsonSerializer.Deserialize<ScheduledJob>(jsonObject, options);
}
else
{
return JsonSerializer.Deserialize<OnDemandJob>(jsonObject, options);
}
}
}
public override void Write(Utf8JsonWriter writer, JobBase value, JsonSerializerOptions options)
{
if(value is ScheduledJob)
{
JsonSerializer.Serialize(writer, value as ScheduledJob, options);
}
else
{
JsonSerializer.Serialize(writer, value as OnDemandJob, options);
}
}
}
What's the counterpart to JObject.FromObject in System.Text.Json
There is an open issue for it.
But now there is no such methods. You can try
using (JsonDocument document = JsonDocument.Parse(JsonSerializer.Serialize(object)))
{
...
}
One more issue
How to find out what type a JsonValue is in System.Text.Json
From the source, it looks like a JsonValue
just wraps a JsonElement
. So you can do .GetValue<JsonElement>()
(which passes this check), and then inspect its ValueKind
property.
Equivalent of JArray.FromObject() in .Net5?
In .NET 5 there is no modifiable JSON Document Object Model built into to System.Text.Json. JsonDocument
is read-only, and System.Text.Json.Nodes
was only introduced in .NET 6. Thus, the easiest way to deserialize, modify and re-serialize free-form JSON in .NET 5 is to deserialize to some partial data model, with unknown values bound into a dictionary.
If you do not care about the order of properties at the root level, you could deserialize to a model with a public object students { get; set; }
property, and bind the remaining elements to a JsonExtensionData
overflow dictionary:
public class RootObject
{
public object students { get; set; }
[System.Text.Json.Serialization.JsonExtensionDataAttribute]
public IDictionary<string, object> ExtensionData { get; set; }
}
Then deserialize, modify and re-serialize as follows:
var students = new List<Student> { /* Initialize these as required... */ };
var dynamicJson = @"{""roomid"":1,""roomcode"":""Code001"",""students"":[1],""contentdata"":""say hello"",""footerdata"":""cookie policy""}";
var root = JsonSerializer.Deserialize<RootObject>(dynamicJson);
root.students = students;
var modifyJson = JsonSerializer.Serialize(root, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true });
Which results in
{
"students": [
{
"id": 1,
"name": "Wilson",
"contactPhone": "123-122-3311",
"medicalRecords": [
{
"id": 101,
"name": "Medial record 101011",
"recordDate": "2021-12-31T00:00:00",
"diseaseLogs": [
{
"id": 18211,
"name": "Patient Log 19292",
"logDate": "2020-01-31T00:00:00"
},
{
"id": 18212,
"name": "Patient Log 2911w",
"logDate": "2020-03-31T00:00:00"
}
]
}
]
}
],
"roomid": 1,
"roomcode": "Code001",
"contentdata": "say hello",
"footerdata": "cookie policy"
}
the students
property must be declared as object
because the input JSON already has an array containing a single integer value; declaring it as public List<Student> students { get; set; }
would result in a deserialization when initially loading the JSON.
Demo fiddle #1 here.
If you do care about the order of properties at the root level, you could deserialize to an OrderedDictionary
(an old order-preserving non-generic dictionary dating from .NET Framework 2.0 which is still around and supported), overwrite the "students"
value, and re-serialize:
var students = new List<Student> { /* Initialize these as required... */ };
var dynamicJson = @"{""roomid"":1,""roomcode"":""Code001"",""students"":[1],""contentdata"":""say hello"",""footerdata"":""cookie policy""}";
var root = JsonSerializer.Deserialize<OrderedDictionary>(dynamicJson);
root["students"] = students;
var modifyJson = JsonSerializer.Serialize(root, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true });
Which results in
{
"roomid": 1,
"roomcode": "Code001",
"students": [
{
"id": 1,
"name": "Wilson",
"contactPhone": "123-122-3311",
"medicalRecords": [
{
"id": 101,
"name": "Medial record 101011",
"recordDate": "2021-12-31T00:00:00",
"diseaseLogs": [
{
"id": 18211,
"name": "Patient Log 19292",
"logDate": "2020-01-31T00:00:00"
},
{
"id": 18212,
"name": "Patient Log 2911w",
"logDate": "2020-03-31T00:00:00"
}
]
}
]
}
],
"contentdata": "say hello",
"footerdata": "cookie policy"
}
Demo fiddle #2 here.
In .NET 6 this all becomes easier through use of the System.Text.Json.Nodes
editable JSON Document Object Model:
var dynamicJson = @"{""roomid"":1,""roomcode"":""Code001"",""students"":[1],""contentdata"":""say hello"",""footerdata"":""cookie policy""}";
var nodes = JsonSerializer.Deserialize<JsonObject>(dynamicJson);
nodes["students"] = JsonSerializer.SerializeToNode(students, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
var modifyJson = nodes.ToString();
But in .NET 5 this is not possible. Demo fiddle #3 here.
Related Topics
C# Convert String from Utf-8 to Iso-8859-1 (Latin1) H
How to Close a Streamwriter Without Closing Its Basestream
How Do Arrays in C# Partially Implement Ilist<T>
At the End of an Async Method, Should I Return or Await
How To: Best Way to Draw Table in Console App (C#)
Fire-And-Forget with Async VS "Old Async Delegate"
Regular Expression to Remove HTML Tags
How to Create 7-Zip Archives with .Net
How to Enable Http Put and Delete for ASP.NET MVC in Iis
How to Get Every Nth Item from a List<T>
How to Detect the Character Encoding of a Text File
Best Way to Access a Control on Another Form in Windows Forms
Win32 API Function to Programmatically Enable/Disable Device
What Operations Are Atomic in C#
Difference Between Events and Delegates and Its Respective Applications
Writing Large Number of Records (Bulk Insert) to Access in .Net/C#