All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opensearch.common.xcontent.JsonToStringXContentParser Maven / Gradle / Ivy

There is a newer version: 2.18.0
Show newest version
/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * The OpenSearch Contributors require contributions made to
 * this file be licensed under the Apache-2.0 license or a
 * compatible open source license.
 */

package org.opensearch.common.xcontent;

import org.opensearch.common.xcontent.json.JsonXContent;
import org.opensearch.core.common.Strings;
import org.opensearch.core.common.bytes.BytesReference;
import org.opensearch.core.xcontent.AbstractXContentParser;
import org.opensearch.core.xcontent.DeprecationHandler;
import org.opensearch.core.xcontent.MediaType;
import org.opensearch.core.xcontent.MediaTypeRegistry;
import org.opensearch.core.xcontent.NamedXContentRegistry;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.core.xcontent.XContentLocation;
import org.opensearch.core.xcontent.XContentParser;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.CharBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedList;

/**
 * JsonToStringParser is the main parser class to transform JSON into stringFields in a XContentParser
 * returns XContentParser with one parent field and subfields
 * fieldName, fieldName._value, fieldName._valueAndPath
 * @opensearch.internal
 */
public class JsonToStringXContentParser extends AbstractXContentParser {
    private final String fieldTypeName;
    private final XContentParser parser;

    private final ArrayList valueList = new ArrayList<>();
    private final ArrayList valueAndPathList = new ArrayList<>();
    private final ArrayList keyList = new ArrayList<>();

    private final XContentBuilder builder = XContentBuilder.builder(JsonXContent.jsonXContent);

    private final NamedXContentRegistry xContentRegistry;

    private final DeprecationHandler deprecationHandler;

    private static final String VALUE_AND_PATH_SUFFIX = "._valueAndPath";
    private static final String VALUE_SUFFIX = "._value";
    private static final String EQUAL_SYMBOL = "=";

    public JsonToStringXContentParser(
        NamedXContentRegistry xContentRegistry,
        DeprecationHandler deprecationHandler,
        XContentParser parser,
        String fieldTypeName
    ) throws IOException {
        super(xContentRegistry, deprecationHandler);
        this.deprecationHandler = deprecationHandler;
        this.xContentRegistry = xContentRegistry;
        this.parser = parser;
        this.fieldTypeName = fieldTypeName;
    }

    public XContentParser parseObject() throws IOException {
        assert currentToken() == Token.START_OBJECT;
        parser.nextToken(); // Skip the outer START_OBJECT. Need to return on END_OBJECT.

        builder.startObject();
        LinkedList path = new LinkedList<>(Collections.singleton(fieldTypeName));
        while (currentToken() != Token.END_OBJECT) {
            parseToken(path, null);
        }
        // deduplication the fieldName,valueList,valueAndPathList
        builder.field(this.fieldTypeName, new HashSet<>(keyList));
        builder.field(this.fieldTypeName + VALUE_SUFFIX, new HashSet<>(valueList));
        builder.field(this.fieldTypeName + VALUE_AND_PATH_SUFFIX, new HashSet<>(valueAndPathList));
        builder.endObject();
        String jString = XContentHelper.convertToJson(BytesReference.bytes(builder), false, MediaTypeRegistry.JSON);
        return JsonXContent.jsonXContent.createParser(this.xContentRegistry, this.deprecationHandler, String.valueOf(jString));
    }

    /**
     * @return true if the child object contains no_null value, false otherwise
     */
    private boolean parseToken(Deque path, String currentFieldName) throws IOException {
        if (path.size() == 1 && processNoNestedValue()) {
            return true;
        }
        boolean isChildrenValueValid = false;
        boolean visitFieldName = false;
        if (this.parser.currentToken() == Token.FIELD_NAME) {
            currentFieldName = this.parser.currentName();
            path.addLast(currentFieldName); // Pushing onto the stack *must* be matched by pop
            visitFieldName = true;
            String parts = currentFieldName;
            while (parts.contains(".")) { // Extract the intermediate keys maybe present in fieldName
                int dotPos = parts.indexOf('.');
                String part = parts.substring(0, dotPos);
                this.keyList.add(part);
                parts = parts.substring(dotPos + 1);
            }
            this.keyList.add(parts); // parts has no dot, so either it's the original fieldName or it's the last part
            this.parser.nextToken(); // advance to the value of fieldName
            isChildrenValueValid = parseToken(path, currentFieldName); // parse the value for fieldName (which will be an array, an object,
                                                                       // or a primitive value)
            path.removeLast(); // Here is where we pop fieldName from the stack (since we're done with the value of fieldName)
            // Note that whichever other branch we just passed through has already ended with nextToken(), so we
            // don't need to call it.
        } else if (this.parser.currentToken() == Token.START_ARRAY) {
            parser.nextToken();
            while (this.parser.currentToken() != Token.END_ARRAY) {
                isChildrenValueValid |= parseToken(path, currentFieldName);
            }
            this.parser.nextToken();
        } else if (this.parser.currentToken() == Token.END_ARRAY) {
            // skip
        } else if (this.parser.currentToken() == Token.START_OBJECT) {
            parser.nextToken();
            while (this.parser.currentToken() != Token.END_OBJECT) {
                isChildrenValueValid |= parseToken(path, currentFieldName);
            }
            this.parser.nextToken();
        } else {
            String parsedValue = parseValue();
            if (parsedValue != null) {
                this.valueList.add(parsedValue);
                this.valueAndPathList.add(Strings.collectionToDelimitedString(path, ".") + EQUAL_SYMBOL + parsedValue);
                isChildrenValueValid = true;
            }
            this.parser.nextToken();
        }

        if (visitFieldName && isChildrenValueValid == false) {
            removeKeyOfNullValue();
        }
        return isChildrenValueValid;
    }

    public void removeKeyOfNullValue() {
        // it means that the value of the sub child (or the last brother) is invalid,
        // we should delete the key from keyList.
        assert keyList.size() > 0;
        this.keyList.remove(keyList.size() - 1);
    }

    private boolean processNoNestedValue() throws IOException {
        if (parser.currentToken() == Token.VALUE_NULL) {
            return true;
        } else if (this.parser.currentToken() == Token.VALUE_STRING
            || this.parser.currentToken() == Token.VALUE_NUMBER
            || this.parser.currentToken() == Token.VALUE_BOOLEAN) {
                String value = this.parser.textOrNull();
                if (value != null) {
                    this.valueList.add(value);
                }
                return true;
            }
        return false;
    }

    private String parseValue() throws IOException {
        switch (this.parser.currentToken()) {
            case VALUE_BOOLEAN:
            case VALUE_NUMBER:
            case VALUE_STRING:
            case VALUE_NULL:
                return this.parser.textOrNull();
            // Handle other token types as needed
            default:
                throw new IOException("Unsupported value token type [" + parser.currentToken() + "]");
        }
    }

    @Override
    public MediaType contentType() {
        return MediaTypeRegistry.JSON;
    }

    @Override
    public Token nextToken() throws IOException {
        return this.parser.nextToken();
    }

    @Override
    public void skipChildren() throws IOException {
        this.parser.skipChildren();
    }

    @Override
    public Token currentToken() {
        return this.parser.currentToken();
    }

    @Override
    public String currentName() throws IOException {
        return this.parser.currentName();
    }

    @Override
    public String text() throws IOException {
        return this.parser.text();
    }

    @Override
    public CharBuffer charBuffer() throws IOException {
        return this.parser.charBuffer();
    }

    @Override
    public Object objectText() throws IOException {
        return this.parser.objectText();
    }

    @Override
    public Object objectBytes() throws IOException {
        return this.parser.objectBytes();
    }

    @Override
    public boolean hasTextCharacters() {
        return this.parser.hasTextCharacters();
    }

    @Override
    public char[] textCharacters() throws IOException {
        return this.parser.textCharacters();
    }

    @Override
    public int textLength() throws IOException {
        return this.parser.textLength();
    }

    @Override
    public int textOffset() throws IOException {
        return this.parser.textOffset();
    }

    @Override
    public Number numberValue() throws IOException {
        return this.parser.numberValue();
    }

    @Override
    public NumberType numberType() throws IOException {
        return this.parser.numberType();
    }

    @Override
    public byte[] binaryValue() throws IOException {
        return this.parser.binaryValue();
    }

    @Override
    public XContentLocation getTokenLocation() {
        return this.parser.getTokenLocation();
    }

    @Override
    protected boolean doBooleanValue() throws IOException {
        return this.parser.booleanValue();
    }

    @Override
    protected short doShortValue() throws IOException {
        return this.parser.shortValue();
    }

    @Override
    protected int doIntValue() throws IOException {
        return this.parser.intValue();
    }

    @Override
    protected long doLongValue() throws IOException {
        return this.parser.longValue();
    }

    @Override
    protected float doFloatValue() throws IOException {
        return this.parser.floatValue();
    }

    @Override
    protected double doDoubleValue() throws IOException {
        return this.parser.doubleValue();
    }

    @Override
    protected BigInteger doBigIntegerValue() throws IOException {
        return this.parser.bigIntegerValue();
    }

    @Override
    public boolean isClosed() {
        return this.parser.isClosed();
    }

    @Override
    public void close() throws IOException {
        this.parser.close();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy