core: split JSON parsing into its own file

This commit is contained in:
Carl Mastrangelo 2018-03-07 17:57:48 -08:00 committed by GitHub
parent eef9add54c
commit 8b9d179798
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 271 additions and 0 deletions

View File

@ -40,6 +40,7 @@ java_library(
":core",
"//context",
"@com_google_code_findbugs_jsr305//jar",
"@com_google_code_gson_gson//jar",
"@com_google_errorprone_error_prone_annotations//jar",
"@com_google_guava_guava//jar",
"@io_opencensus_opencensus_api//jar",

View File

@ -0,0 +1,145 @@
/*
* Copyright 2018, gRPC Authors All rights reserved.
*
* 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 io.grpc.internal;
import static com.google.common.base.Preconditions.checkState;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Parses JSON with as few preconceived notions as possible.
*/
final class JsonParser {
private static final Logger logger = Logger.getLogger(JsonParser.class.getName());
private JsonParser() {}
@SuppressWarnings("unchecked")
static Object parse(String raw) throws IOException {
JsonReader jr = new JsonReader(new StringReader(raw));
try {
return parseRecursive(jr);
} finally {
try {
jr.close();
} catch (IOException e) {
logger.log(Level.WARNING, "Failed to close", e);
}
}
}
private static Object parseRecursive(JsonReader jr) throws IOException {
checkState(jr.hasNext(), "unexpected end of JSON");
switch (jr.peek()) {
case BEGIN_ARRAY:
return parseJsonArray(jr);
case BEGIN_OBJECT:
return parseJsonObject(jr);
case STRING:
return jr.nextString();
case NUMBER:
return jr.nextDouble();
case BOOLEAN:
return jr.nextBoolean();
case NULL:
return parseJsonNull(jr);
default:
throw new IllegalStateException("Bad token: " + jr.getPath());
}
}
private static Map<String, Object> parseJsonObject(JsonReader jr) throws IOException {
jr.beginObject();
Map<String, Object> obj = new LinkedHashMap<String, Object>();
while (jr.hasNext()) {
String name = jr.nextName();
switch (jr.peek()) {
case BEGIN_ARRAY:
obj.put(name, parseJsonArray(jr));
break;
case BEGIN_OBJECT:
obj.put(name, parseJsonObject(jr));
break;
case STRING:
obj.put(name, jr.nextString());
break;
case NUMBER:
obj.put(name, jr.nextDouble());
break;
case BOOLEAN:
obj.put(name, jr.nextBoolean());
break;
case NULL:
obj.put(name, parseJsonNull(jr));
break;
default:
throw new IllegalStateException("Bad token: " + jr.getPath());
}
}
checkState(jr.peek() == JsonToken.END_OBJECT, "Bad token: " + jr.getPath());
jr.endObject();
return Collections.unmodifiableMap(obj);
}
private static List<Object> parseJsonArray(JsonReader jr) throws IOException {
jr.beginArray();
List<Object> array = new ArrayList<Object>();
while (jr.hasNext()) {
switch (jr.peek()) {
case BEGIN_ARRAY:
array.add(parseJsonArray(jr));
break;
case BEGIN_OBJECT:
array.add(parseJsonObject(jr));
break;
case STRING:
array.add(jr.nextString());
break;
case NUMBER:
array.add(jr.nextDouble());
break;
case BOOLEAN:
array.add(jr.nextBoolean());
break;
case NULL:
array.add(parseJsonNull(jr));
break;
default:
throw new IllegalStateException("Bad token: " + jr.getPath());
}
}
checkState(jr.peek() == JsonToken.END_ARRAY, "Bad token: " + jr.getPath());
jr.endArray();
return Collections.unmodifiableList(array);
}
private static Void parseJsonNull(JsonReader jr) throws IOException {
jr.nextNull();
return null;
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright 2018, gRPC Authors All rights reserved.
*
* 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 io.grpc.internal;
import static org.junit.Assert.assertEquals;
import com.google.gson.stream.MalformedJsonException;
import java.io.EOFException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link JsonParser}.
*/
@RunWith(JUnit4.class)
public class JsonParserTest {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void emptyObject() throws IOException {
assertEquals(new LinkedHashMap<String, Object>(), JsonParser.parse("{}"));
}
@Test
public void emptyArray() throws IOException {
assertEquals(new ArrayList<Object>(), JsonParser.parse("[]"));
}
@Test
public void intNumber() throws IOException {
assertEquals(Double.valueOf("1"), JsonParser.parse("1"));
}
@Test
public void doubleNumber() throws IOException {
assertEquals(Double.valueOf("1.2"), JsonParser.parse("1.2"));
}
@Test
public void longNumber() throws IOException {
assertEquals(Double.valueOf("9999999999"), JsonParser.parse("9999999999"));
}
@Test
public void booleanValue() throws IOException {
assertEquals(true, JsonParser.parse("true"));
}
@Test
public void nullValue() throws IOException {
assertEquals(null, JsonParser.parse("null"));
}
@Test
public void nanFails() throws IOException {
thrown.expect(MalformedJsonException.class);
JsonParser.parse("NaN");
}
@Test
public void objectEarlyEnd() throws IOException {
thrown.expect(MalformedJsonException.class);
JsonParser.parse("{foo:}");
}
@Test
public void earlyEndArray() throws IOException {
thrown.expect(EOFException.class);
JsonParser.parse("[1, 2, ");
}
@Test
public void arrayMissingElement() throws IOException {
thrown.expect(MalformedJsonException.class);
JsonParser.parse("[1, 2, ]");
}
@Test
public void objectMissingElement() throws IOException {
thrown.expect(MalformedJsonException.class);
JsonParser.parse("{1: ");
}
@Test
public void objectNoName() throws IOException {
thrown.expect(MalformedJsonException.class);
JsonParser.parse("{: 1");
}
@Test
public void objectStringName() throws IOException {
LinkedHashMap<String, Object> expected = new LinkedHashMap<String, Object>();
expected.put("hi", Double.valueOf("2"));
assertEquals(expected, JsonParser.parse("{\"hi\": 2}"));
}
}