0

Say I want to output a JSON data structure to a requester. This JSON data would:

  1. Be free-form (Basically, it could be a value, object or even an array)
  2. 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,

  1. I take in a source,

  2. 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.

  3. I stash it as an ICollection

  4. 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; }
    }
    
Nicholas
  • 1,883
  • 21
  • 39
  • Are you just trying to re-serialize the value of a `JsonElement`? If so you can use `JsonSerializer` to do it, see e.g. https://dotnetfiddle.net/wjphKy. Don't forget to clone them if they need to persist past the lifetime of the `JsonDocument`. – dbc Aug 09 '20 at 18:48
  • @dbc I have almost achieved what I want through: ```jsonDoc.RootElement.DescendantPropertyValues() .Where(v => v.ValueKind.IsPrimitive()) .Select(v => v.Clone().GetRawText()).ToList()``` problem is, the array's structure seemed to have merged. I have a sample code for you to refer to in case you need to find out what payload i'm processing. https://github.com/nozomi-ai/websocket-sharp/blob/feature/sync-fix/WebSocketSharp.NetCore.Tests/WebSocketClientTests.cs Refer to AsyncClientTestListeners – Nicholas Aug 10 '20 at 01:56
  • https://pastebin.com/HVyr2zyA Sample here @dbc Instead of getting ```[{"e":"24hrTicker","E":1597024964584,"s":"ETHBTC","p":"-0.00062900"...``` I'm getting ```[ "[{\"e\":\"24hrTicker\",\"E\":1597024913563,\"s\":\"ETHBTC\",\"p\":\"-0.00056100\",``` As if something is helping me before it gets sent to frontend.. – Nicholas Aug 10 '20 at 02:01
  • Well that sounds like you are double-serializing. I.e. you are returning a JSON string from your API, which then gets serialized a second time. You can just return an `IEnumerable` and it will get serialized once, properly, without needing to serialize it yourself. – dbc Aug 10 '20 at 02:09
  • @dbc I've updated my content on the post for more granularity. Anyway, the .GetRawText() approach I did (On top of your methods) was the closest I got. It gave me what I needed but it destroyed the Array and Object structures. – Nicholas Aug 10 '20 at 02:28

0 Answers0