key

xod/json/key

Extracts a value from an object in a JSON stream
key
@/key
Extracts a value from an object in a JSON stream
NAMEstring
Key name
IN1@/parsed-json-chunk
Parsed chunk of a JSON document
IN2pulse
Process the incoming parsed chunk
key
IN1
IN2
OUT1
OUT2
NAME
OUT2pulse
Pulses when a new parsed chunk is produced
OUT1@/parsed-json-chunk
Parsed chunk of an extracted value

C++ implementation

#pragma XOD evaluate_on_pin disable
#pragma XOD evaluate_on_pin enable input_IN2

nodespace {
    using ParserState = xod::json_parser::ParserState;

    enum Mode : uint8_t { waitingForKey, matchingKey, passingValue };
}

node {
    Iterator<char> searchedKeyIt = Iterator<char>::nil();
    Mode mode = waitingForKey;
    ParserState prevParsedState;
    uint16_t depth = 0;
    bool wasFound = false;

    void evaluate(Context ctx) {
        if (!isInputDirty<input_IN2>(ctx)) return;

        auto parsed = getValue<input_IN1>(ctx);

        if (parsed.state == ParserState::START_OBJECT || parsed.state == ParserState::START_ARRAY)
            depth++;

        bool shouldDecreaseDepth = parsed.state == ParserState::AFTER_OBJECT || parsed.state == ParserState::AFTER_ARRAY || parsed.state == ParserState::DONE;
        // trailing ']' and '}' that are passed to indicate end of the value should not decrease depth
        if (shouldDecreaseDepth && depth > 0)
            depth--;

        switch (mode) {
            case waitingForKey: {
                bool expectingAKey = prevParsedState == ParserState::START_OBJECT ||
                                     prevParsedState == ParserState::IN_OBJECT ||
                                     prevParsedState == ParserState::AFTER_VALUE;
                if (depth == 1 && expectingAKey && parsed.state == ParserState::IN_STRING) {
                    auto keyNameStr = getValue<input_NAME>(ctx);
                    searchedKeyIt = keyNameStr.iterate();
                    mode = matchingKey;
                    // the first char of a key is a '"'. we don't need it, so just get ready for the next character
                }
                break;
            }
            case matchingKey: {
                // matched the searched key
                if (!searchedKeyIt && parsed.state == ParserState::END_KEY) {
                    mode = passingValue;
                    wasFound = true;
                    break;
                }

                bool oneOfComparedStringsHasEnded = !searchedKeyIt || parsed.state == ParserState::AFTER_KEY;
                if (oneOfComparedStringsHasEnded || *searchedKeyIt != parsed.character) {
                    mode = waitingForKey;
                    break;
                }

                ++(searchedKeyIt);
                break;
            }
            case passingValue: {
                // skipping delimiter between key and value,
                // waiting for a value to start
                if (parsed.state == ParserState::AFTER_KEY) break;

                bool valueEnded = depth <= 1 && (parsed.state == ParserState::IN_OBJECT || parsed.state == ParserState::AFTER_ARRAY || parsed.state == ParserState::AFTER_OBJECT);
                if (valueEnded || parsed.state == ParserState::DONE) {
                    mode = waitingForKey;
                }

                emitValue<output_OUT1>(ctx, parsed);
                emitValue<output_OUT2>(ctx, true);
                break;
            }
        }

        // reached the end of object
        if (depth == 0 && (parsed.state == ParserState::AFTER_OBJECT || parsed.state == ParserState::DONE)) {
            if (!wasFound) raiseError(ctx);

            wasFound = false;
        }

        prevParsedState = parsed.state;
    }
}