In an earlier question of mine I asked how to populate an existing object using System.Text.Json.
One of the great answers showed a solution parsing the json string with JsonDocument and enumerate it with EnumerateObject.
Over time my json string evolved and does now also contain an array of objects, and when parsing that with the code from the linked answer it throws the following exception:
The requested operation requires an element of type 'Object', but the target element has type 'Array'.
I figured out that one can in one way or the other look for the JsonValueKind.Array, and do something like this
if (json.ValueKind.Equals(JsonValueKind.Array))
{
foreach (var item in json.EnumerateArray())
{
foreach (var property in item.EnumerateObject())
{
await OverwriteProperty(???);
}
}
}
but I can't make that work.
How to do this, and as a generic solution?
I would like to get "Result 1", where array items gets added/updated, and "Result 2" (when passing a variable), where the whole array gets replaced.
For "Result 2" I assume one can detect if (JsonValueKind.Array)) in the OverwriteProperty method, and where/how to pass the "replaceArray" variable? ... while iterating the array or the objects?
Some sample data:
Json string initial
{
"Title": "Startpage",
"Links": [
{
"Id": 10,
"Text": "Start",
"Link": "/index"
},
{
"Id": 11,
"Text": "Info",
"Link": "/info"
}
]
}
Json string to add/update
{
"Head": "Latest news",
"Links": [
{
"Id": 11,
"Text": "News",
"Link": "/news"
},
{
"Id": 21,
"Text": "More News",
"Link": "/morenews"
}
]
}
Result 1
{
"Title": "Startpage",
"Head": "Latest news"
"Links": [
{
"Id": 10,
"Text": "Start",
"Link": "/indexnews"
},
{
"Id": 11,
"Text": "News",
"Link": "/news"
},
{
"Id": 21,
"Text": "More news",
"Link": "/morenews"
}
]
}
Result 2
{
"Title": "Startpage",
"Head": "Latest news"
"Links": [
{
"Id": 11,
"Text": "News",
"Link": "/news"
},
{
"Id": 21,
"Text": "More News",
"Link": "/morenews"
}
]
}
Classes
public class Pages
{
public string Title { get; set; }
public string Head { get; set; }
public List<Links> Links { get; set; }
}
public class Links
{
public int Id { get; set; }
public string Text { get; set; }
public string Link { get; set; }
}
C# code:
public async Task PopulateObjectAsync(object target, string source, Type type, bool replaceArrays = false)
{
using var json = JsonDocument.Parse(source).RootElement;
if (json.ValueKind.Equals(JsonValueKind.Array))
{
foreach (var item in json.EnumerateArray())
{
foreach (var property in item.EnumerateObject())
{
await OverwriteProperty(???, replaceArray); //use "replaceArray" here ?
}
}
}
else
{
foreach (var property in json.EnumerateObject())
{
await OverwriteProperty(target, property, type, replaceArray); //use "replaceArray" here ?
}
}
return;
}
public async Task OverwriteProperty(object target, JsonProperty updatedProperty, Type type, bool replaceArrays)
{
var propertyInfo = type.GetProperty(updatedProperty.Name);
if (propertyInfo == null)
{
return;
}
var propertyType = propertyInfo.PropertyType;
object parsedValue;
if (propertyType.IsValueType)
{
parsedValue = JsonSerializer.Deserialize(
updatedProperty.Value.GetRawText(),
propertyType);
}
else if (replaceArrays && "property is JsonValueKind.Array") //pseudo code sample
{
// use same code here as in above "IsValueType" ?
}
else
{
parsedValue = propertyInfo.GetValue(target);
await PopulateObjectAsync(
parsedValue,
updatedProperty.Value.GetRawText(),
propertyType);
}
propertyInfo.SetValue(target, parsedValue);
}
Well, If you don't care how the arrays are written, I have a simple solution. Create a new JSON within 2 phases 1 loop for new properties and 1 loop for the updates:
var sourceJson = @"
{
""Title"": ""Startpage"",
""Links"": [
{
""Id"": 10,
""Text"": ""Start"",
""Link"": ""/index""
},
{
""Id"": 11,
""Text"": ""Info"",
""Link"": ""/info""
}
]
}";
var updateJson = @"
{
""Head"": ""Latest news"",
""Links"": [
{
""Id"": 11,
""Text"": ""News"",
""Link"": ""/news""
},
{
""Id"": 21,
""Text"": ""More News"",
""Link"": ""/morenews""
}
]
}
";
using var source = JsonDocument.Parse(sourceJson);
using var update = JsonDocument.Parse(updateJson);
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream);
writer.WriteStartObject();
// write non existing properties
foreach (var prop in update.RootElement.EnumerateObject().Where(prop => !source.RootElement.TryGetProperty(prop.Name, out _)))
{
prop.WriteTo(writer);
}
// make updates for existing
foreach (var prop in source.RootElement.EnumerateObject())
{
if (update.RootElement.TryGetProperty(prop.Name, out var overwrite))
{
writer.WritePropertyName(prop.Name);
overwrite.WriteTo(writer);
}
else
{
prop.WriteTo(writer);
}
}
writer.WriteEndObject();
writer.Flush();
var resultJson = Encoding.UTF8.GetString(stream.ToArray());
Console.WriteLine(resultJson);
Output :
{
"Head":"Latest news",
"Title":"Startpage",
"Links":[
{
"Id":11,
"Text":"News",
"Link":"/news"
},
{
"Id":21,
"Text":"More News",
"Link":"/morenews"
}
]
}