Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an option to accept only JSON Objects #33

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,9 @@ static CapturingDirectMemberNameList of(final List<String> memberNames) {
JsonValue[] captureFromParser(
final JsonParser jacksonParser,
final InternalJsonValueReader valueReader) throws IOException {
final JsonToken firstToken;
try {
final JsonToken firstToken = jacksonParser.nextToken();
if (firstToken == null) {
return null;
}
if (firstToken != JsonToken.START_OBJECT) {
throw new JsonParseException("Failed to parse JSON: Expected JSON Object, but " + firstToken.toString());
}
firstToken = jacksonParser.nextToken();
} catch (final com.fasterxml.jackson.core.JsonParseException ex) {
throw new JsonParseException("Failed to parse JSON", ex);
} catch (final IOException ex) {
Expand All @@ -65,6 +60,15 @@ JsonValue[] captureFromParser(
throw new JsonParseException("Failed to parse JSON", ex);
}

if (firstToken == null) {
return null;
}
// The value must be always a JSON object regardless of |onlyJsonObjects| when capturing by direct member names.
if (firstToken != JsonToken.START_OBJECT) {
valueReader.skipJsonValue(jacksonParser, firstToken);
throw new InvalidJsonValueException("Expected JSON Object, but " + firstToken.toString());
}

final JsonValue[] values = new JsonValue[this.size];
for (int i = 0; i < values.length; i++) {
values[i] = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.core.JsonToken;
import java.io.IOException;
import java.util.List;
import org.embulk.spi.json.JsonValue;
Expand Down Expand Up @@ -71,6 +72,7 @@ static CapturingJsonPointerList of(final List<JsonPointer> pointers) {
* @param parser {@link com.fasterxml.jackson.core.JsonParser} to read from
* @return an array of captured JSON values
* @throws IOException when failing to read
* @throws InvalidJsonValueException if the JSON value is not a JSON object while it is configured to accept only JSON objects
*/
@Override
JsonValue[] captureFromParser(final JsonParser parser, final InternalJsonValueReader valueReader) throws IOException {
Expand All @@ -89,6 +91,10 @@ JsonValue[] captureFromParser(final JsonParser parser, final InternalJsonValueRe
;
}

if (valueReader.isOnlyJsonObjects() && capturer.firstToken() != JsonToken.START_OBJECT) {
throw new InvalidJsonValueException("Expected JSON Object, but " + capturer.firstToken());
}

return capturer.peekValues();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ JsonValue[] captureFromParser(
if (value == null) {
return null;
}
if (valueReader.isOnlyJsonObjects() && !value.isJsonObject()) {
throw new InvalidJsonValueException("Expected JSON Object, but " + value.getEntityType().toString());
}

final JsonValue[] values = new JsonValue[1];
values[0] = value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,22 @@

final class InternalJsonValueReader {
InternalJsonValueReader(
final boolean isOnlyJsonObjects,
final boolean hasLiteralsWithNumbers,
final boolean hasFallbacksForUnparsableNumbers,
final double defaultDouble,
final long defaultLong) {
this.isOnlyJsonObjects = isOnlyJsonObjects;
this.hasLiteralsWithNumbers = hasLiteralsWithNumbers;
this.hasFallbacksForUnparsableNumbers = hasFallbacksForUnparsableNumbers;
this.defaultDouble = defaultDouble;
this.defaultLong = defaultLong;
}

boolean isOnlyJsonObjects() {
return this.isOnlyJsonObjects;
}

boolean hasLiteralsWithNumbers() {
return this.hasLiteralsWithNumbers;
}
Expand Down Expand Up @@ -203,7 +209,7 @@ private JsonValue readJsonValue(final JsonParser jacksonParser, final JsonToken
}
}

private void skipJsonValue(final JsonParser jacksonParser, final JsonToken token) throws IOException {
void skipJsonValue(final JsonParser jacksonParser, final JsonToken token) throws IOException {
switch (token) {
case VALUE_NULL:
case VALUE_TRUE:
Expand Down Expand Up @@ -295,6 +301,7 @@ private long getLongValue(final JsonParser jacksonParser) throws IOException {
}
}

private final boolean isOnlyJsonObjects;
private final boolean hasLiteralsWithNumbers;
private final boolean hasFallbacksForUnparsableNumbers;
private final double defaultDouble;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2023 The Embulk project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.embulk.util.json;

import org.embulk.spi.DataException;

/**
* Represents an Exception for a JSON value that is invalid against the requirement.
*/
public class InvalidJsonValueException extends DataException {
/**
* Constructs a new {@link InvalidJsonValueException} with the specified detail message.
*
* @param message the detail message
*/
public InvalidJsonValueException(final String message) {
super(message);
}

/**
* Constructs a new {@link InvalidJsonValueException} with the specified detail message and cause.
*
* @param message the detail message
* @param cause the cause
*/
public InvalidJsonValueException(final String message, final Throwable cause) {
super(message, cause);
}
}
22 changes: 20 additions & 2 deletions src/main/java/org/embulk/util/json/JsonValueParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@
public final class JsonValueParser implements Closeable {
private JsonValueParser(
final com.fasterxml.jackson.core.JsonParser jacksonParser,
final boolean onlyJsonObjects,
final int depthToFlattenJsonArrays,
final boolean hasLiteralsWithNumbers,
final boolean hasFallbacksForUnparsableNumbers,
final double defaultDouble,
final long defaultLong) {
this.jacksonParser = Objects.requireNonNull(jacksonParser);
this.valueReader = new InternalJsonValueReader(
hasLiteralsWithNumbers, hasFallbacksForUnparsableNumbers, defaultDouble, defaultLong);
onlyJsonObjects, hasLiteralsWithNumbers, hasFallbacksForUnparsableNumbers, defaultDouble, defaultLong);
this.onlyJsonObjects = onlyJsonObjects;
this.depthToFlattenJsonArrays = depthToFlattenJsonArrays;
this.hasLiteralsWithNumbers = hasLiteralsWithNumbers;
this.hasFallbacksForUnparsableNumbers = hasFallbacksForUnparsableNumbers;
Expand All @@ -56,6 +58,7 @@ public static final class Builder {
Builder(final JsonFactory factory) {
this.factory = Objects.requireNonNull(factory);
this.root = null;
this.onlyJsonObjects = false;
this.depthToFlattenJsonArrays = 0;
this.hasLiteralsWithNumbers = false;
this.hasFallbacksForUnparsableNumbers = false;
Expand Down Expand Up @@ -87,6 +90,11 @@ public Builder root(final String root) {
return this;
}

public Builder onlyJsonObjects() {
this.onlyJsonObjects = true;
return this;
}

/**
* Sets the depth to flatten JSON Arrays to parse.
*
Expand Down Expand Up @@ -140,6 +148,7 @@ public Builder fallbackForUnparsableNumbers(final double defaultDouble, final lo
public JsonValueParser build(final String json) throws IOException {
return new JsonValueParser(
buildJacksonParser(json),
this.onlyJsonObjects,
this.depthToFlattenJsonArrays,
this.hasLiteralsWithNumbers,
this.hasFallbacksForUnparsableNumbers,
Expand All @@ -156,6 +165,7 @@ public JsonValueParser build(final String json) throws IOException {
public JsonValueParser build(final InputStream jsonStream) throws IOException {
return new JsonValueParser(
buildJacksonParser(jsonStream),
this.onlyJsonObjects,
this.depthToFlattenJsonArrays,
this.hasLiteralsWithNumbers,
this.hasFallbacksForUnparsableNumbers,
Expand Down Expand Up @@ -195,6 +205,7 @@ private com.fasterxml.jackson.core.JsonParser extendJacksonParser(final com.fast
private final JsonFactory factory;

private JsonPointer root;
private boolean onlyJsonObjects;
private int depthToFlattenJsonArrays;
private boolean hasLiteralsWithNumbers;
private boolean hasFallbacksForUnparsableNumbers;
Expand Down Expand Up @@ -240,9 +251,14 @@ public static Builder builder(final JsonFactory jsonFactory) {
* @return the JSON value, or {@code null} if the parser reaches at the end of input in the beginning
* @throws IOException if failing to read JSON
* @throws JsonParseException if failing to parse JSON
* @throws InvalidJsonValueException if the JSON value is not a JSON object while it is configured to accept only JSON objects
*/
public JsonValue readJsonValue() throws IOException {
return this.valueReader.read(this.jacksonParser);
final JsonValue value = this.valueReader.read(this.jacksonParser);
if (this.onlyJsonObjects && !value.isJsonObject()) {
throw new InvalidJsonValueException("Expected JSON Object, but " + value.getEntityType().toString());
}
return value;
}

/**
Expand All @@ -251,6 +267,7 @@ public JsonValue readJsonValue() throws IOException {
* @return an array of the captured JSON values, or {@code null} if the parser reaches at the end of input in the beginning
* @throws IOException if failing to read JSON
* @throws JsonParseException if failing to parse JSON
* @throws InvalidJsonValueException if the JSON value is not a JSON object while it is configured to accept only JSON objects
*/
public JsonValue[] captureJsonValues(final CapturingPointers capturingPointers) throws IOException {
return capturingPointers.captureFromParser(this.jacksonParser, this.valueReader);
Expand All @@ -269,6 +286,7 @@ public final void close() throws IOException {
private final com.fasterxml.jackson.core.JsonParser jacksonParser;
private final InternalJsonValueReader valueReader;

private final boolean onlyJsonObjects;
private final int depthToFlattenJsonArrays;
private final boolean hasLiteralsWithNumbers;
private final boolean hasFallbacksForUnparsableNumbers;
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/org/embulk/util/json/TreeBasedCapturer.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class TreeBasedCapturer {
this.builderStack = new ArrayDeque<>();

this.hasFinished = false;
this.firstToken = null;

this.values = new JsonValue[size];
for (int i = 0; i < this.values.length; i++) {
Expand Down Expand Up @@ -98,7 +99,9 @@ boolean next() throws IOException {

// Deepen the pointer stack when the token is a scalar value, START_ARRAY, or START_OBJECT.
if (token.isScalarValue() || token.isStructStart()) {
if (!this.parsingStack.isEmpty()) {
if (this.parsingStack.isEmpty()) {
this.firstToken = token;
} else {
final ParsingContext context = this.parsingStack.getFirst();
if (context.isObject()) {
final String propertyName = context.getPropertyName();
Expand Down Expand Up @@ -252,6 +255,10 @@ JsonValue[] peekValues() {
return this.values;
}

JsonToken firstToken() {
return this.firstToken;
}

private double getDoubleValue() throws IOException {
try {
return this.parser.getDoubleValue();
Expand Down Expand Up @@ -452,4 +459,5 @@ private static Map.Entry<String, JsonValue>[] toArray(final ArrayList<Map.Entry<
private final JsonValue[] values;

private boolean hasFinished;
private JsonToken firstToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@

public class TestCapturingDirectMemberNameList {
@Test
public void testRead1() throws Exception {
public void testOrdinaryRead() throws Exception {
final JsonFactory factory = new JsonFactory();
final JsonParser parser = factory.createParser(
"{\"foo\":{\"ignored\":[1,2,{},\"skipped\"]},\"bar\":[true,false],\"baz\":null,\"qux\":{\"hoge\":\"fuga\"}}");
final InternalJsonValueReader reader = new InternalJsonValueReader(false, false, 0.0, 0L);
final InternalJsonValueReader reader = new InternalJsonValueReader(false, false, false, 0.0, 0L);

final CapturingDirectMemberNameList capturingMembers1 = capturingMembers(
"bar",
Expand Down
Loading