Exemple #1
0
        internal void ResolveValue(IJsonPlusNode child)
        {
            if (child.Type == JsonPlusType.Empty)
            {
                Remove(child);
            }
            else if (Type == JsonPlusType.Empty)
            {
                Type = child.Type;
            }
            else if (Type != child.Type)
            {
                JsonPlusSubstitution sub = (JsonPlusSubstitution)child;
                throw JsonPlusParserException.Create(sub, sub.Path, string.Format(RS.SubstitutionSiblingTypeMismatch, Type, child.Type));
            }

            if (Parent is JsonPlusArray)
            {
                // #todo
                ((JsonPlusArray)Parent).ResolveValue(this);
                return;
            }

            ((JsonPlusObjectMember)Parent).ResolveValue(this);
        }
Exemple #2
0
        /// <summary>
        /// Parses the Json+ source code specified into structured objects.
        /// </summary>
        /// <param name="source">The source code that conforms to Json+ specification.</param>
        /// <param name="includeCallback">Callback used to resolve the `include` directive.</param>
        /// <param name="resolveSubstitutions">Resolve substitution directives.</param>
        /// <param name="resolveEnv">Try to resolve environment variables. Does nothing if <paramref name="resolveSubstitutions"/> is `false`.</param>
        /// <returns></returns>
        private JsonPlusRoot ParseSource(string source, bool resolveSubstitutions, bool resolveEnv, IncludeCallbackAsync includeCallback)
        {
            if (string.IsNullOrWhiteSpace(source))
            {
                throw new JsonPlusParserException(string.Format(RS.SourceEmptyError, nameof(source)));
            }

            if (includeCallback != null)
            {
                _includeCallback = includeCallback;
            }

            try
            {
                _tokens = new JPlusTokenizer(source).Tokenize();
                _root   = new JsonPlusValue(null);
                ParseTokens();
                if (resolveSubstitutions)
                {
                    ResolveAllSubstitution(resolveEnv);
                }
            }
            catch (JsonPlusTokenizerException e)
            {
                throw JsonPlusParserException.Create(e, null, string.Format(RS.TokenizeError, e.Message), e);
            }
            catch (JsonPlusException e)
            {
                throw JsonPlusParserException.Create(_tokens.Current, Path, e.Message, e);
            }

            return(new JsonPlusRoot(_root, _substitutions));
        }
Exemple #3
0
        private IJsonPlusNode ParseSelfAssignArray(IJsonPlusNode owner)
        {
            // sanity check
            if (_tokens.Current.Type != TokenType.SelfAssignment)
            {
                throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInArray, TokenType.SelfAssignment, _tokens.Current.Type));
            }

            // consume += operator token
            ConsumeWhitelines();

            JsonPlusArray currentArray = new JsonPlusArray(owner);

            switch (_tokens.Current.Type)
            {
            case TokenType.Include:
            case TokenType.OptionalInclude:
                // #todo
                currentArray.Add(ParseInclude(currentArray));
                break;

            case TokenType.StartOfArray:
                return(ParseArray(owner));

            case TokenType.StartOfObject:
                // #todo
                currentArray.Add(ParseObject(currentArray));
                break;

            case TokenType.LiteralValue:
                if (_tokens.Current.IsNonSignificant())
                {
                    ConsumeWhitelines();
                }
                if (_tokens.Current.Type != TokenType.LiteralValue)
                {
                    break;
                }

                currentArray.Add(ParseValue(currentArray));
                return(currentArray);

            case TokenType.OptionalSubstitution:
            case TokenType.Substitution:
                JsonPlusPath         pointerPath = JsonPlusPath.Parse(_tokens.Current.Value);
                JsonPlusSubstitution sub         = new JsonPlusSubstitution(owner, pointerPath, _tokens.Current,
                                                                            _tokens.Current.Type == TokenType.Substitution);
                _substitutions.Add(sub);
                _tokens.Next();
                return(sub);

            default:
                throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInArray, TokenType.EndOfArray, _tokens.Current.Type));
            }

            return(currentArray);
        }
Exemple #4
0
        private void ParseTokens()
        {
            if (_tokens.Current.IsNonSignificant())
            {
                ConsumeWhitelines();
            }

            while (_tokens.Current.Type != TokenType.EndOfFile)
            {
                switch (_tokens.Current.Type)
                {
                case TokenType.Include:
                case TokenType.OptionalInclude:
                    _root.Add(ParseInclude(_root));
                    break;

                // may contain one array and one array only
                case TokenType.StartOfArray:
                    _root.Add(ParseArray(_root));
                    break;

                case TokenType.StartOfObject:
                    _root.Add(ParseObject(_root).GetObject());
                    break;

                case TokenType.LiteralValue:
                    if (_tokens.Current.IsNonSignificant())
                    {
                        ConsumeWhitelines();
                    }
                    if (_tokens.Current.Type != TokenType.LiteralValue)
                    {
                        break;
                    }

                    _root.Add(ParseObject(_root).GetObject());
                    break;

                case TokenType.Comment:
                case TokenType.EndOfLine:
                case TokenType.EndOfFile:
                case TokenType.EndOfObject:
                case TokenType.EndOfArray:
                    _tokens.Next();
                    break;

                default:
                    throw JsonPlusParserException.Create(_tokens.Current, null, string.Format(RS.IllegalTokenType, _tokens.Current.Type), null);
                }
            }
        }
Exemple #5
0
        private JsonPlusObjectMember ParseObjectMember(JsonPlusObject owner)
        {
            // sanity check
            if (_tokens.Current.Type != TokenType.LiteralValue)
            {
                throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInMember, TokenType.LiteralValue, _tokens.Current.Type));
            }

            JsonPlusPath pathDelta = ParseKey();

            if (_tokens.Current.IsNonSignificant())
            {
                ConsumeWhitelines();
            }

            // sanity check
            if (_tokens.Current.Type != TokenType.Assignment &&
                _tokens.Current.Type != TokenType.StartOfObject &&
                _tokens.Current.Type != TokenType.SelfAssignment)
            {
                throw JsonPlusParserException.Create(_tokens.Current, Path,
                                                     string.Format(RS.UnexpectedTokenWith3AltInMember,
                                                                   TokenType.Assignment, TokenType.StartOfObject, TokenType.SelfAssignment,
                                                                   _tokens.Current.Type));
            }

            // sanity check
            if (pathDelta == null || pathDelta.Count == 0)
            {
                throw JsonPlusParserException.Create(_tokens.Current, Path, RS.ObjectMemberPathUnspecified);
            }

            List <JsonPlusObjectMember> childInPath = owner.TraversePath(pathDelta);

            Path.AddRange(pathDelta);
            JsonPlusObjectMember currentField = childInPath[childInPath.Count - 1];

            JsonPlusValue parsedValue = ParseValue(currentField);

            foreach (JsonPlusSubstitution removedSub in currentField.SetValue(parsedValue))
            {
                _substitutions.Remove(removedSub);
            }

            Path.RemoveRange(Path.Count - pathDelta.Count, pathDelta.Count);
            return(childInPath[0]);
        }
Exemple #6
0
        // parse path value
        private JsonPlusPath ParseKey()
        {
            while (_tokens.Current.LiteralType == LiteralTokenType.Whitespace)
            {
                _tokens.Next();
            }

            // sanity check
            if (_tokens.Current.Type != TokenType.LiteralValue)
            {
                throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedKeyType, TokenType.LiteralValue, _tokens.Current.Type));
            }

            if (_tokens.Current.IsNonSignificant())
            {
                ConsumeWhitelines();
            }
            if (_tokens.Current.Type != TokenType.LiteralValue)
            {
                return(null);
            }

            TokenizeResult keyTokens = new TokenizeResult();

            while (_tokens.Current.Type == TokenType.LiteralValue)
            {
                keyTokens.Add(_tokens.Current);
                _tokens.Next();
            }

            keyTokens.Reverse();
            while (keyTokens.Count > 0 && keyTokens[0].LiteralType == LiteralTokenType.Whitespace)
            {
                keyTokens.RemoveAt(0);
            }
            keyTokens.Reverse();

            keyTokens.Add(new Token(string.Empty, TokenType.EndOfFile, null));

            return(JsonPlusPath.FromTokens(keyTokens));
        }
Exemple #7
0
        internal static JsonPlusPath FromTokens(TokenizeResult tokens)
        {
            if (tokens == null)
            {
                throw new ArgumentNullException(nameof(tokens));
            }

            List <string> result = new List <string>();
            StringBuilder sb     = new StringBuilder();

            while (tokens.Current.Type == TokenType.LiteralValue)
            {
                switch (tokens.Current.LiteralType)
                {
                case LiteralTokenType.TripleQuotedLiteralValue:
                    throw JsonPlusParserException.Create(tokens.Current, null, RS.TripleQuoteUnsupportedInPath);

                case LiteralTokenType.QuotedLiteralValue:
                    // Normalize quoted keys, remove the quotes if the key doesn't need them.
                    //sb.Append(tokens.Current.Value.NeedQuotes() ? $"\"{tokens.Current.Value}\"" : tokens.Current.Value);
                    sb.Append(tokens.Current.Value);
                    break;

                default:
                    string[] split = tokens.Current.Value.Split('.');
                    for (int i = 0; i < split.Length - 1; ++i)
                    {
                        sb.Append(split[i]);
                        result.Add(sb.ToString());
                        sb.Clear();
                    }
                    sb.Append(split[split.Length - 1]);
                    break;
                }
                tokens.Next();
            }
            result.Add(sb.ToString());
            return(new JsonPlusPath(result));
        }
Exemple #8
0
        private void ResolveAllSubstitution(bool resolveEnv)
        {
            foreach (JsonPlusSubstitution sub in _substitutions)
            {
                // Retrieve value
                JsonPlusValue res;
                try
                {
                    res = ResolveSubstitution(sub);
                }
                catch (JsonPlusException e)
                {
                    throw JsonPlusParserException.Create(sub, sub.Path, string.Format(RS.SubstitutionError, e.Message), e);
                }

                if (res != null)
                {
                    sub.ResolvedValue = res;
                    continue;
                }

                if (resolveEnv)
                {
                    // Try to pull value from environment
                    string envValue = null;
                    try
                    {
                        envValue = Environment.GetEnvironmentVariable(sub.Path.Value);
                    }
                    catch (Exception)
                    {
                        // ignored
                    }

                    if (envValue != null)
                    {
                        // undefined value resolved to an environment variable
                        res = new JsonPlusValue(sub.Parent);
                        if (envValue.NeedQuotes())
                        {
                            res.Add(new QuotedStringValue(sub.Parent, envValue));
                        }
                        else
                        {
                            res.Add(new UnquotedStringValue(sub.Parent, envValue));
                        }

                        sub.ResolvedValue = res;
                        continue;
                    }
                }

                // ${ throws exception if it is not resolved
                if (sub.Required)
                {
                    throw JsonPlusParserException.Create(sub, sub.Path, string.Format(RS.UnresolvedSubstitution, sub.Path));
                }

                sub.ResolvedValue = new EmptyValue(sub.Parent);
            }
        }
Exemple #9
0
        /// <summary>
        /// Retrieves the next array token from the tokenizer.
        /// </summary>
        /// <returns>An array of elements retrieved from the token.</returns>
        private JsonPlusArray ParseArray(IJsonPlusNode owner)
        {
            JsonPlusArray currentArray = new JsonPlusArray(owner);

            // consume start of array token
            ConsumeWhitelines();

            IJsonPlusNode lastValue = null;
            bool          parsing   = true;

            while (parsing)
            {
                switch (_tokens.Current.Type)
                {
                case TokenType.Include:
                case TokenType.OptionalInclude:
                    if (lastValue != null)
                    {
                        throw JsonPlusParserException.Create(_tokens.Current, Path,
                                                             string.Format(RS.UnexpectedTokenWith2AltInArray,
                                                                           TokenType.ArraySeparator, TokenType.EndOfLine,
                                                                           _tokens.Current.Type));
                    }

                    lastValue = ParseInclude(currentArray);
                    break;

                case TokenType.StartOfArray:
                    if (lastValue != null)
                    {
                        throw JsonPlusParserException.Create(_tokens.Current, Path,
                                                             string.Format(RS.UnexpectedTokenWith2AltInArray,
                                                                           TokenType.ArraySeparator, TokenType.EndOfLine,
                                                                           _tokens.Current.Type));
                    }

                    // Array inside of arrays are parsed as values because it can be value concatenated with another array.
                    lastValue = ParseValue(currentArray);
                    break;

                case TokenType.StartOfObject:
                    if (lastValue != null)
                    {
                        throw JsonPlusParserException.Create(_tokens.Current, Path,
                                                             string.Format(RS.UnexpectedTokenWith2AltInArray,
                                                                           TokenType.ArraySeparator, TokenType.EndOfLine,
                                                                           _tokens.Current.Type));
                    }

                    lastValue = ParseObject(currentArray);
                    break;

                case TokenType.LiteralValue:
                    if (_tokens.Current.IsNonSignificant())
                    {
                        ConsumeWhitespace();
                    }
                    if (_tokens.Current.Type != TokenType.LiteralValue)
                    {
                        break;
                    }

                    if (lastValue != null)
                    {
                        throw JsonPlusParserException.Create(_tokens.Current, Path,
                                                             string.Format(RS.UnexpectedTokenWith2AltInArray,
                                                                           TokenType.ArraySeparator, TokenType.EndOfLine,
                                                                           _tokens.Current.Type));
                    }

                    lastValue = ParseValue(currentArray);
                    break;

                case TokenType.OptionalSubstitution:
                case TokenType.Substitution:
                    if (lastValue != null)
                    {
                        throw JsonPlusParserException.Create(_tokens.Current, Path,
                                                             string.Format(RS.UnexpectedTokenWith2AltInArray,
                                                                           TokenType.ArraySeparator, TokenType.EndOfLine,
                                                                           _tokens.Current.Type));
                    }

                    lastValue = ParseValue(currentArray);

                    break;

                case TokenType.Comment:
                case TokenType.EndOfLine:
                    if (lastValue == null)
                    {
                        ConsumeWhitelines();
                        break;
                    }

                    currentArray.Add(lastValue);
                    lastValue = null;
                    ConsumeWhitelines();
                    break;

                case TokenType.ArraySeparator:
                    if (lastValue == null)
                    {
                        throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.BadArrayType, _tokens.Current.Type));
                    }

                    currentArray.Add(lastValue);
                    lastValue = null;
                    ConsumeWhitelines();
                    break;

                case TokenType.EndOfArray:
                    if (lastValue != null)
                    {
                        currentArray.Add(lastValue);
                        lastValue = null;
                    }
                    parsing = false;
                    break;

                default:
                    throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInArray, TokenType.EndOfArray, _tokens.Current.Type));
                }
            }

            // Consume end of array token
            _tokens.Next();
            return(currentArray);
        }
Exemple #10
0
        /// <summary>
        /// Retrieves the next value token from the tokenizer and appends it
        /// to the supplied element <paramref name="owner"/>.
        /// </summary>
        /// <param name="owner">The element to append the next token.</param>
        /// <exception cref="Exception">End of file reached while trying to read a value</exception>
        private JsonPlusValue ParseValue(IJsonPlusNode owner)
        {
            JsonPlusValue value   = new JsonPlusValue(owner);
            bool          parsing = true;

            while (parsing)
            {
                switch (_tokens.Current.Type)
                {
                case TokenType.Include:
                case TokenType.OptionalInclude:
                    value.Add(ParseInclude(value));
                    break;

                case TokenType.LiteralValue:
                    // Consume leading whitespaces.
                    if (_tokens.Current.IsNonSignificant())
                    {
                        ConsumeWhitespace();
                    }
                    if (_tokens.Current.Type != TokenType.LiteralValue)
                    {
                        break;
                    }

                    while (_tokens.Current.Type == TokenType.LiteralValue)
                    {
                        value.Add(JsonPlusLiteralValue.Create(value, _tokens.Current));
                        _tokens.Next();
                    }
                    break;

                case TokenType.StartOfObject:
                    value.Add(ParseObject(value));
                    break;

                case TokenType.StartOfArray:
                    value.Add(ParseArray(value));
                    break;

                case TokenType.OptionalSubstitution:
                case TokenType.Substitution:
                    JsonPlusPath         pointerPath = JsonPlusPath.Parse(_tokens.Current.Value);
                    JsonPlusSubstitution sub         = new JsonPlusSubstitution(value, pointerPath, _tokens.Current,
                                                                                _tokens.Current.Type == TokenType.Substitution);
                    _substitutions.Add(sub);
                    value.Add(sub);
                    _tokens.Next();
                    break;

                case TokenType.EndOfObject:
                case TokenType.EndOfArray:
                    parsing = false;
                    break;

                // comments automatically stop value parsing.
                case TokenType.Comment:
                    ConsumeWhitelines();
                    parsing = false;
                    break;

                case TokenType.EndOfLine:
                    parsing = false;
                    break;

                case TokenType.EndOfFile:
                case TokenType.ArraySeparator:
                    parsing = false;
                    break;

                case TokenType.SelfAssignment:
                    JsonPlusSubstitution subAssign = new JsonPlusSubstitution(value, new JsonPlusPath(Path), _tokens.Current, false);
                    _substitutions.Add(subAssign);
                    value.Add(subAssign);

                    value.Add(ParseSelfAssignArray(value));
                    parsing = false;
                    break;

                case TokenType.Assignment:
                    ConsumeWhitelines();
                    break;

                default:
                    throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.ErrAtUnexpectedToken, _tokens.Current.Type));
                }
            }

            // trim trailing whitespace if result is a literal
            if (value.Type == JsonPlusType.Literal)
            {
                if (value[value.Count - 1] is WhitespaceValue)
                {
                    value.RemoveAt(value.Count - 1);
                }
            }
            return(value);
        }
Exemple #11
0
        // The owner in this context can be either an object or an array.
        private JsonPlusObject ParseObject(IJsonPlusNode owner)
        {
            JsonPlusObject jpObject = new JsonPlusObject(owner);

            if (_tokens.Current.Type != TokenType.StartOfObject &&
                _tokens.Current.Type != TokenType.LiteralValue &&
                _tokens.Current.Type != TokenType.Include)
            {
                throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInObject,
                                                                                          TokenType.StartOfObject, TokenType.LiteralValue,
                                                                                          _tokens.Current.Type));
            }

            bool headless = true;

            if (_tokens.Current.Type == TokenType.StartOfObject)
            {
                headless = false;
                ConsumeWhitelines();
            }

            IJsonPlusNode lastValue = null;
            bool          parsing   = true;

            while (parsing)
            {
                switch (_tokens.Current.Type)
                {
                case TokenType.Include:
                case TokenType.OptionalInclude:
                    if (lastValue != null)
                    {
                        throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInObject,
                                                                                                  TokenType.ArraySeparator, TokenType.EndOfLine,
                                                                                                  _tokens.Current.Type));
                    }

                    lastValue = ParseInclude(jpObject);
                    break;

                case TokenType.LiteralValue:
                    if (_tokens.Current.IsNonSignificant())
                    {
                        ConsumeWhitespace();
                    }
                    if (_tokens.Current.Type != TokenType.LiteralValue)
                    {
                        break;
                    }

                    if (lastValue != null)
                    {
                        throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInObject,
                                                                                                  TokenType.ArraySeparator, TokenType.EndOfLine,
                                                                                                  _tokens.Current.Type));
                    }

                    lastValue = ParseObjectMember(jpObject);
                    break;

                // TODO: can an object be declared floating without being assigned to a field?
                //case TokenType.StartOfObject:
                case TokenType.Comment:
                case TokenType.EndOfLine:
                    switch (lastValue)
                    {
                    case null:
                        ConsumeWhitelines();
                        break;

                    case JsonPlusObjectMember _:
                        break;

                    default:
                        ((JsonPlusValue)jpObject.Parent).Add(lastValue.GetObject());
                        break;
                    }
                    lastValue = null;
                    ConsumeWhitelines();
                    break;

                case TokenType.ArraySeparator:
                    switch (lastValue)
                    {
                    case null:
                        throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenWith2AltInObject,
                                                                                                  TokenType.Assignment, TokenType.StartOfObject,
                                                                                                  _tokens.Current.Type));

                    case JsonPlusObjectMember _:
                        break;

                    default:
                        ((JsonPlusValue)jpObject.Parent).Add(lastValue.GetObject());
                        break;
                    }
                    lastValue = null;
                    ConsumeWhitelines();
                    break;

                case TokenType.EndOfObject:
                case TokenType.EndOfFile:
                    if (headless && _tokens.Current.Type != TokenType.EndOfFile)
                    {
                        throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInObject, TokenType.EndOfFile, _tokens.Current.Type));
                    }

                    if (!headless && _tokens.Current.Type != TokenType.EndOfObject)
                    {
                        throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.UnexpectedTokenInObject, TokenType.EndOfFile, _tokens.Current.Type));
                    }

                    switch (lastValue)
                    {
                    case null:
                        break;

                    case JsonPlusObjectMember _:
                        break;

                    default:
                        ((JsonPlusValue)jpObject.Parent).Add(lastValue.GetObject());
                        break;
                    }
                    lastValue = null;
                    parsing   = false;
                    break;

                default:
                    throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.ErrAtUnexpectedTokenInObject, _tokens.Current.Type));
                }
            }

            if (_tokens.Current.Type == TokenType.EndOfObject)
            {
                _tokens.Next();
            }

            return(jpObject);
        }
Exemple #12
0
        private IJsonPlusNode ParseInclude(IJsonPlusNode owner)
        {
            // sanity check
            if (_tokens.Current.Type != TokenType.Include &&
                _tokens.Current.Type != TokenType.OptionalInclude)
            {
                throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.InvalidTokenOnParseInclude, _tokens.Current.Type));
            }

            bool   required     = _tokens.Current.Type == TokenType.Include;
            string fileName     = null;
            Token  includeToken = _tokens.Current;

            List <TokenType> expectedTokens = new List <TokenType>(new[]
            {
                TokenType.LiteralValue,
                TokenType.CloseBracket,
                TokenType.EndOfLine
            });

            bool parsing = true;

            while (parsing)
            {
                if (!_tokens.GetNextSignificant(expectedTokens.ToArray()))
                {
                    throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.InvalidTokenInInclude, _tokens.Current.Type), null);
                }

                switch (_tokens.Current.Type)
                {
                case TokenType.LiteralValue:
                    if (_tokens.Current.IsNonSignificant())
                    {
                        ConsumeWhitespace();
                    }
                    if (_tokens.Current.Type != TokenType.LiteralValue)
                    {
                        break;
                    }

                    if (_tokens.Current.LiteralType != LiteralTokenType.QuotedLiteralValue)
                    {
                        throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.BadIncludeFileName, LiteralTokenType.QuotedLiteralValue, _tokens.Current.LiteralType));
                    }

                    fileName = _tokens.Current.Value;
                    expectedTokens.Remove(TokenType.LiteralValue);

                    parsing = false;
                    break;

                default:
                    throw JsonPlusParserException.Create(_tokens.Current, Path, string.Format(RS.ErrAtUnexpectedToken, _tokens.Current.Type));
                }
            }

            if (fileName == null)
            {
                throw JsonPlusParserException.Create(_tokens.Current, Path, RS.FileNameMissingInInclude);
            }

            // Consume the last token
            _tokens.Next();

            string includeSrc = _includeCallback(fileName).ConfigureAwait(false).GetAwaiter().GetResult();

            if (string.IsNullOrWhiteSpace(includeSrc))
            {
                if (required)
                {
                    throw JsonPlusParserException.Create(includeToken, Path, RS.IncludeReturnEmptyError);
                }

                return(new EmptyValue(owner));
            }

            JsonPlusRoot includeRoot = new JsonPlusParser().ParseSource(includeSrc, false, false, _includeCallback);

            if (owner != null &&
                owner.Type != JsonPlusType.Empty &&
                owner.Type != includeRoot.Value.Type)
            {
                throw JsonPlusParserException.Create(includeToken, Path, string.Format(RS.IncludeMergeTypeMismatch, owner.Type, includeRoot.Value.Type));
            }

            // fixup the substitution, add the current path as a prefix to the substitution path
            foreach (JsonPlusSubstitution substitution in includeRoot.Substitutions)
            {
                substitution.Path.InsertRange(0, Path);
            }
            _substitutions.AddRange(includeRoot.Substitutions);

            // reparent the value returned by the callback to the owner of the include declaration
            return(includeRoot.Value.Clone(owner));
        }