private (bool handled, int consumed) HandleDataBlock(FromBodyPropertyInputFormatterContext context, ref DataBlockMode blockMode, ref int depth, ref JsonReaderState readerState, ReadOnlySpan <byte> data, bool finalBlock, ref string currentProperty, Dictionary <string, List <byte[]> > propertyData)
        {
            var jr = new Utf8JsonReader(data, finalBlock, readerState);

            int start = 0;

            while (jr.Read())
            {
                var jrt = jr.TokenType;

                if (blockMode == DataBlockMode.Start)
                {
                    if (jrt == JsonTokenType.StartObject)
                    {
                        blockMode = DataBlockMode.Body;
                    }
                    else if (jrt == JsonTokenType.Comment)
                    {
                        continue;
                    }
                    else
                    {
                        throw new Exception("Unexpected json token '" + jrt + "'.");
                    }
                }
                else if (blockMode == DataBlockMode.End)
                {
                    if (jrt == JsonTokenType.Comment)
                    {
                        continue;
                    }
                    else
                    {
                        throw new Exception("Unexpected json token '" + jr.TokenType + "', expected end of object.");
                    }
                }
                else if (blockMode == DataBlockMode.Body)
                {
                    if (jrt == JsonTokenType.EndObject)
                    {
                        blockMode = DataBlockMode.End;
                    }
                    else if (jrt == JsonTokenType.Comment)
                    {
                        continue;
                    }
                    else if (jrt == JsonTokenType.PropertyName)
                    {
                        currentProperty = jr.GetString();
                        if (propertyData.ContainsKey(currentProperty))
                        {
                            throw new Exception("Duplicate property: " + currentProperty);
                        }
                        propertyData.Add(currentProperty, new List <byte[]>());
                        start     = checked ((int)jr.BytesConsumed);
                        blockMode = DataBlockMode.Property;
                    }
                    else
                    {
                        throw new Exception("Unexpected json token '" + jr.TokenType + "', expected property.");
                    }
                }
                else if (blockMode == DataBlockMode.Property)
                {
                    if (IsValueToken(jrt))
                    {
                        propertyData[currentProperty].Add(data.Slice(start, checked ((int)jr.BytesConsumed) - start).ToArray());
                        blockMode       = DataBlockMode.Body;
                        currentProperty = null;
                        start           = checked ((int)jr.BytesConsumed);
                    }
                    else if (jrt == JsonTokenType.Comment)
                    {
                        continue;
                    }
                    else if (jrt == JsonTokenType.StartObject)
                    {
                        depth     = jr.CurrentDepth;
                        blockMode = DataBlockMode.SkipObject;
                    }
                    else if (jrt == JsonTokenType.StartArray)
                    {
                        depth     = jr.CurrentDepth;
                        blockMode = DataBlockMode.SkipArray;
                    }
                    else
                    {
                        throw new Exception("Unexpected json token '" + jr.TokenType + "', expected property value.");
                    }
                }
                else if (blockMode == DataBlockMode.SkipObject)
                {
                    if (jrt == JsonTokenType.EndObject && jr.CurrentDepth == depth)
                    {
                        propertyData[currentProperty].Add(data.Slice(start, checked ((int)jr.BytesConsumed) - start).ToArray());
                        blockMode       = DataBlockMode.Body;
                        currentProperty = null;
                        start           = checked ((int)jr.BytesConsumed);
                    }
                }
                else if (blockMode == DataBlockMode.SkipArray)
                {
                    if (jrt == JsonTokenType.EndArray && jr.CurrentDepth == depth)
                    {
                        propertyData[currentProperty].Add(data.Slice(start, checked ((int)jr.BytesConsumed) - start).ToArray());
                        blockMode       = DataBlockMode.Body;
                        currentProperty = null;
                        start           = checked ((int)jr.BytesConsumed);
                    }
                }
                else
                {
                    throw new Exception("Unexpected state");
                }
            }

            if (!jr.IsFinalBlock)
            {
                readerState = jr.CurrentState;
                var consumed = checked ((int)jr.BytesConsumed);
                if (consumed - start > 0 && (blockMode == DataBlockMode.SkipObject || blockMode == DataBlockMode.SkipArray))
                {
                    propertyData[currentProperty].Add(data.Slice(start, consumed - start).ToArray());
                }
                return(false, consumed);
            }

            if (blockMode != DataBlockMode.End)
            {
                throw new Exception("More data expected");
            }

            return(true, 0);
        }
        public async Task <(bool success, bool noValue, object model, Exception exception)> ReadAsync(FromBodyPropertyInputFormatterContext context, Func <Func <Stream, JsonSerializerOptions, Task>, Task> readRequestBody, Encoding encoding)
        {
            if (!string.Equals(_actionId, context.ActionId, StringComparison.Ordinal))
            {
                throw new InvalidOperationException("Invalid action '" + context.ActionId + "', initialize for action '" + _actionId + "'.");
            }

            if (!HasReadRequestBody)
            {
                HasReadRequestBody = true;
                await readRequestBody(async (s, options) => {
                    _options          = options;
                    int bufferSize    = 0x4096;
                    var propertyData  = new Dictionary <string, List <byte[]> >(StringComparer.Ordinal);
                    var readerOptions = new JsonReaderOptions {
                        AllowTrailingCommas = options.AllowTrailingCommas,
                        CommentHandling     = options.ReadCommentHandling,
                        MaxDepth            = options.MaxDepth
                    };
                    var readerState      = new JsonReaderState(readerOptions);
                    var buffer           = ArrayPool <byte> .Shared.Rent(bufferSize);
                    Memory <byte> memory = buffer.AsMemory(0, bufferSize);
                    int length;
                    int offset             = 0;
                    string currentProperty = null;
                    int depth = 0;
                    DataBlockMode blockMode = DataBlockMode.Start;
                    bool handled            = false;
                    bool finalBlock         = false;
                    int consumed            = 0;
                    try {
                        length     = await s.ReadAsync(memory);
                        finalBlock = length == 0;
                        do
                        {
                            (handled, consumed) = HandleDataBlock(context, ref blockMode, ref depth, ref readerState, memory.Span.Slice(offset, length), finalBlock, ref currentProperty, propertyData);
                            offset += consumed;
                            if (!handled)
                            {
                                int bytesInBuffer = length - offset;
                                if (bytesInBuffer == 0)
                                {
                                    // read more data
                                    length     = await s.ReadAsync(memory);
                                    finalBlock = length == 0;
                                    offset     = 0;
                                }
                                else if ((uint)bytesInBuffer > ((uint)bufferSize / 2))
                                {
                                    // expand buffer
                                    bufferSize  = (bufferSize < (int.MaxValue / 2)) ? bufferSize * 2 : int.MaxValue;
                                    var buffer2 = ArrayPool <byte> .Shared.Rent(bufferSize);

                                    // copy the unprocessed data
                                    Buffer.BlockCopy(buffer, offset, buffer2, 0, bytesInBuffer);

                                    // return previous buffer
                                    ArrayPool <byte> .Shared.Return(buffer);
                                    buffer = buffer2;
                                    memory = buffer.AsMemory(0, bufferSize);

                                    // read more data
                                    length     = await s.ReadAsync(memory.Slice(bytesInBuffer));
                                    finalBlock = length == 0;
                                    length    += bytesInBuffer;
                                    offset     = 0;
                                }
                                else
                                {
                                    Buffer.BlockCopy(buffer, offset, buffer, 0, bytesInBuffer);

                                    // read more data
                                    length     = await s.ReadAsync(memory.Slice(bytesInBuffer));
                                    finalBlock = length == 0;
                                    length    += bytesInBuffer;
                                    offset     = 0;
                                }
                            }
                        }while (!handled);
                    }
                    finally {
                        ArrayPool <byte> .Shared.Return(buffer);
                    }

                    foreach ((var key, var value) in propertyData)
                    {
                        _values.Add(key, value.SelectMany(z => z).ToArray());
                    }
                });
            }

            if (!_keys.TryGetValue(context.FieldName, out var propertyType))
            {
                throw new InvalidOperationException("Unknown property '" + context.FieldName + "' requested.");
            }
            else if (_values.TryGetValue(context.FieldName, out var propertyData))
            {
                try {
                    var v = JsonSerializer.Deserialize(propertyData, propertyType, _options);
                    return(true, false, v, null);
                }
                catch (Exception ex) {
                    return(false, false, null, ex);
                }
            }

            return(true, true, null, null);
        }