Say I want to output a JSON data structure to a requester. This JSON data would:
- Be free-form (Basically, it could be a value, object or even an array)
- Be returned to the frontend directly with no serialization (Frontend is a JS stack, returning the JSON payload without string serialization would allow it to explicit cast on its own.)
I've been looking around StackOverflow and have found a nice snippet by @dbc:
/// <summary>
/// Adapted from dbc answer https://stackoverflow.com/a/60997251/3744182
/// To https://stackoverflow.com/questions/60994574/how-to-extract-all-values-for-all-jsonproperty-objects-with-a-specified-name-fro
///
/// </summary>
public static class JsonExtensions
{
public static IEnumerable<JsonElement> DescendantPropertyValues(this JsonElement element)
{
var query = RecursiveEnumerableExtensions.Traverse(
(Name: (string) null, Value: element),
t =>
{
switch (t.Value.ValueKind)
{
case JsonValueKind.Array:
return t.Value.EnumerateArray().Select(i => ((string) null, i));
case JsonValueKind.Object:
return t.Value.EnumerateObject().Select(p => (p.Name, p.Value));
default:
return Enumerable.Empty<(string, JsonElement)>();
}
}, false)
.Select(t => t.Value);
return query;
}
}
public static class RecursiveEnumerableExtensions
{
// Rewritten from the answer by Eric Lippert https://stackoverflow.com/users/88656/eric-lippert
// to "Efficient graph traversal with LINQ - eliminating recursion" https://stackoverflow.com/questions/10253161/efficient-graph-traversal-with-linq-eliminating-recursion
// to ensure items are returned in the order they are encountered.
public static IEnumerable<T> Traverse<T>(
T root,
Func<T, IEnumerable<T>> children, bool includeSelf = true)
{
if (includeSelf)
yield return root;
var stack = new Stack<IEnumerator<T>>();
try
{
stack.Push(children(root).GetEnumerator());
while (stack.Count != 0)
{
var enumerator = stack.Peek();
if (!enumerator.MoveNext())
{
stack.Pop();
enumerator.Dispose();
}
else
{
yield return enumerator.Current;
stack.Push(children(enumerator.Current).GetEnumerator());
}
}
}
finally
{
foreach (var enumerator in stack)
enumerator.Dispose();
}
}
}
With these methods, I was able to output any data structure to the requester easily. But the problem is that the values returned in each property simply contain ValueKind
.
So, how do we, instead of returning ValueKind return the actual property (Be it an object, array, etc) instead?
Even easier, if there's a method that exists which emulates what Serialize()
does but without the '' inclusions and all, that'll be perfect for the frontend because this scenario omits the requirement of frontends to Parse()
the incoming serialized string, allowing it to 'cast' on its own.
Current code flow:
Data comes in, I setup an ICollection named concatPayload
(I need it to be a collection because I need to process other datasets/datum if more comes in and sometimes it may or may not be JSON. But if it's not JSON, it'll be converted systemically.)
var jsonDoc = JsonDocument.Parse(args.Data);
concatPayload.Add(jsonDoc.RootElement.DescendantPropertyValues()
.Where(v => v.ValueKind.IsPrimitive())
.Select(v => v.Clone()).ToList());
I execute the code above recommended by dbc.
return Ok(await _api.Dispatch(vm)); // The Dispatch method runs the concatPayload population flow.
Simply put,
I take in a source,
I process it, validate if its a JSON or not. If it is, I'll do the above-mentioned parsing to validate. If not, I'll convert it over to JSON.
I stash it as an ICollection
I return the entire ViewModel
public class DispatchViewModel { /// /// If this pops up, user must have done something bad /// public string? ErrorReason { get; set; }
/// <summary> /// The raw payload of the object, in JSON. /// </summary> public ICollection<object> Payload { get; set; } }