Skip to content

Commit

Permalink
Add an option to accept only JSON Objects
Browse files Browse the repository at this point in the history
  • Loading branch information
dmikurube committed Aug 31, 2023
1 parent 1872ce6 commit cad1f19
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 26 deletions.
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
43 changes: 43 additions & 0 deletions src/main/java/org/embulk/util/json/InvalidJsonValueException.java
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

0 comments on commit cad1f19

Please sign in to comment.