JsonWriter.getPath().

This commit is contained in:
jwilson
2015-06-09 23:00:40 -04:00
parent cf56907470
commit 39dc305a14
6 changed files with 318 additions and 66 deletions

View File

@@ -59,7 +59,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
throw new AssertionError();
} catch (InvocationTargetException e) {
if (e.getCause() instanceof IOException) throw (IOException) e.getCause();
throw new JsonDataException(e.getCause()); // TODO: more context?
throw new JsonDataException(e.getCause() + " at " + writer.getPath());
}
}
}
@@ -77,7 +77,7 @@ final class AdapterMethodsFactory implements JsonAdapter.Factory {
throw new AssertionError();
} catch (InvocationTargetException e) {
if (e.getCause() instanceof IOException) throw (IOException) e.getCause();
throw new JsonDataException(e.getCause()); // TODO: more context?
throw new JsonDataException(e.getCause() + " at " + reader.getPath());
}
}
}

View File

@@ -248,14 +248,6 @@ public final class JsonReader implements Closeable {
stack[stackSize++] = JsonScope.EMPTY_DOCUMENT;
}
/*
* The path members. It corresponds directly to stack: At indices where the
* stack contains an object (EMPTY_OBJECT, DANGLING_NAME or NONEMPTY_OBJECT),
* pathNames contains the name at this scope. Where it contains an array
* (EMPTY_ARRAY, NONEMPTY_ARRAY) pathIndices contains the current index in
* that array. Otherwise the value is undefined, and we take advantage of that
* by incrementing pathIndices when doing so isn't useful.
*/
private String[] pathNames = new String[32];
private int[] pathIndices = new int[32];
@@ -1269,30 +1261,7 @@ public final class JsonReader implements Closeable {
* the current location in the JSON value.
*/
public String getPath() {
StringBuilder result = new StringBuilder().append('$');
for (int i = 0, size = stackSize; i < size; i++) {
switch (stack[i]) {
case JsonScope.EMPTY_ARRAY:
case JsonScope.NONEMPTY_ARRAY:
result.append('[').append(pathIndices[i]).append(']');
break;
case JsonScope.EMPTY_OBJECT:
case JsonScope.DANGLING_NAME:
case JsonScope.NONEMPTY_OBJECT:
result.append('.');
if (pathNames[i] != null) {
result.append(pathNames[i]);
}
break;
case JsonScope.NONEMPTY_DOCUMENT:
case JsonScope.EMPTY_DOCUMENT:
case JsonScope.CLOSED:
break;
}
}
return result.toString();
return JsonScope.getPath(stackSize, stack, pathNames, pathIndices);
}
/**

View File

@@ -15,53 +15,65 @@
*/
package com.squareup.moshi;
/**
* Lexical scoping elements within a JSON reader or writer.
*/
/** Lexical scoping elements within a JSON reader or writer. */
final class JsonScope {
/**
* An array with no elements requires no separators or newlines before
* it is closed.
*/
/** An array with no elements requires no separators or newlines before it is closed. */
static final int EMPTY_ARRAY = 1;
/**
* A array with at least one value requires a comma and newline before
* the next element.
*/
/** A array with at least one value requires a comma and newline before the next element. */
static final int NONEMPTY_ARRAY = 2;
/**
* An object with no name/value pairs requires no separators or newlines
* before it is closed.
*/
/** An object with no name/value pairs requires no separators or newlines before it is closed. */
static final int EMPTY_OBJECT = 3;
/**
* An object whose most recent element is a key. The next element must
* be a value.
*/
/** An object whose most recent element is a key. The next element must be a value. */
static final int DANGLING_NAME = 4;
/**
* An object with at least one name/value pair requires a comma and
* newline before the next element.
*/
/** An object with at least one name/value pair requires a separator before the next element. */
static final int NONEMPTY_OBJECT = 5;
/**
* No object or array has been started.
*/
/** No object or array has been started. */
static final int EMPTY_DOCUMENT = 6;
/**
* A document with at an array or object.
*/
/** A document with at an array or object. */
static final int NONEMPTY_DOCUMENT = 7;
/**
* A document that's been closed and cannot be accessed.
*/
/** A document that's been closed and cannot be accessed. */
static final int CLOSED = 8;
/**
* Renders the path in a JSON document to a string. The {@code pathNames} and {@code pathIndices}
* parameters corresponds directly to stack: At indices where the stack contains an object
* (EMPTY_OBJECT, DANGLING_NAME or NONEMPTY_OBJECT), pathNames contains the name at this scope.
* Where it contains an array (EMPTY_ARRAY, NONEMPTY_ARRAY) pathIndices contains the current index
* in that array. Otherwise the value is undefined, and we take advantage of that by incrementing
* pathIndices when doing so isn't useful.
*/
static String getPath(int stackSize, int[] stack, String[] pathNames, int[] pathIndices) {
StringBuilder result = new StringBuilder().append('$');
for (int i = 0, size = stackSize; i < size; i++) {
switch (stack[i]) {
case EMPTY_ARRAY:
case NONEMPTY_ARRAY:
result.append('[').append(pathIndices[i]).append(']');
break;
case EMPTY_OBJECT:
case DANGLING_NAME:
case NONEMPTY_OBJECT:
result.append('.');
if (pathNames[i] != null) {
result.append(pathNames[i]);
}
break;
case NONEMPTY_DOCUMENT:
case EMPTY_DOCUMENT:
case CLOSED:
break;
}
}
return result.toString();
}
}

View File

@@ -160,6 +160,9 @@ public final class JsonWriter implements Closeable, Flushable {
push(EMPTY_DOCUMENT);
}
private String[] pathNames = new String[32];
private int[] pathIndices = new int[32];
/**
* A string containing a full set of spaces for a single level of
* indentation, or null for no pretty printing.
@@ -290,6 +293,7 @@ public final class JsonWriter implements Closeable, Flushable {
*/
private JsonWriter open(int empty, String openBracket) throws IOException {
beforeValue(true);
pathIndices[stackSize] = 0;
push(empty);
sink.writeUtf8(openBracket);
return this;
@@ -310,6 +314,8 @@ public final class JsonWriter implements Closeable, Flushable {
}
stackSize--;
pathNames[stackSize] = null; // Free the last path name so that it can be garbage collected!
pathIndices[stackSize - 1]++;
if (context == nonempty) {
newline();
}
@@ -360,6 +366,7 @@ public final class JsonWriter implements Closeable, Flushable {
throw new IllegalStateException("JsonWriter is closed.");
}
deferredName = name;
pathNames[stackSize - 1] = name;
return this;
}
@@ -384,6 +391,7 @@ public final class JsonWriter implements Closeable, Flushable {
writeDeferredName();
beforeValue(false);
string(value);
pathIndices[stackSize - 1]++;
return this;
}
@@ -403,6 +411,7 @@ public final class JsonWriter implements Closeable, Flushable {
}
beforeValue(false);
sink.writeUtf8("null");
pathIndices[stackSize - 1]++;
return this;
}
@@ -415,6 +424,7 @@ public final class JsonWriter implements Closeable, Flushable {
writeDeferredName();
beforeValue(false);
sink.writeUtf8(value ? "true" : "false");
pathIndices[stackSize - 1]++;
return this;
}
@@ -432,6 +442,7 @@ public final class JsonWriter implements Closeable, Flushable {
writeDeferredName();
beforeValue(false);
sink.writeUtf8(Double.toString(value));
pathIndices[stackSize - 1]++;
return this;
}
@@ -444,6 +455,7 @@ public final class JsonWriter implements Closeable, Flushable {
writeDeferredName();
beforeValue(false);
sink.writeUtf8(Long.toString(value));
pathIndices[stackSize - 1]++;
return this;
}
@@ -467,6 +479,7 @@ public final class JsonWriter implements Closeable, Flushable {
}
beforeValue(false);
sink.writeUtf8(string);
pathIndices[stackSize - 1]++;
return this;
}
@@ -598,4 +611,12 @@ public final class JsonWriter implements Closeable, Flushable {
throw new IllegalStateException("Nesting problem.");
}
}
/**
* Returns a <a href="http://goessner.net/articles/JsonPath/">JsonPath</a> to
* the current location in the JSON value.
*/
public String getPath() {
return JsonScope.getPath(stackSize, stack, pathNames, pathIndices);
}
}

View File

@@ -233,6 +233,37 @@ public final class AdapterMethodsTest {
@interface Nullable {
}
@Test public void adapterThrows() throws Exception {
Moshi moshi = new Moshi.Builder()
.add(new ExceptionThrowingPointJsonAdapter())
.build();
JsonAdapter<Point[]> arrayOfPointAdapter = moshi.adapter(Point[].class).lenient();
try {
arrayOfPointAdapter.toJson(new Point[] { null, null, new Point(0, 0) });
fail();
} catch (JsonDataException expected) {
assertThat(expected.getMessage())
.isEqualTo("java.lang.Exception: pointToJson fail! at $[2]");
}
try {
arrayOfPointAdapter.fromJson("[null,null,[0,0]]");
fail();
} catch (JsonDataException expected) {
assertThat(expected.getMessage())
.isEqualTo("java.lang.Exception: pointFromJson fail! at $[2]");
}
}
static class ExceptionThrowingPointJsonAdapter {
@ToJson void pointToJson(JsonWriter writer, Point point) throws Exception {
throw new Exception("pointToJson fail!");
}
@FromJson Point pointFromJson(JsonReader reader) throws Exception {
throw new Exception("pointFromJson fail!");
}
}
static class Point {
final int x;
final int y;

View File

@@ -0,0 +1,219 @@
/*
* Copyright (C) 2014 Google Inc.
*
* 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 com.squareup.moshi;
import java.io.IOException;
import java.math.BigInteger;
import okio.Buffer;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public final class JsonWriterPathTest {
@Test public void path() throws IOException {
JsonWriter writer = new JsonWriter(new Buffer());
assertThat(writer.getPath()).isEqualTo("$");
writer.beginObject();
assertThat(writer.getPath()).isEqualTo("$.");
writer.name("a");
assertThat(writer.getPath()).isEqualTo("$.a");
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$.a[0]");
writer.value(2);
assertThat(writer.getPath()).isEqualTo("$.a[1]");
writer.value(true);
assertThat(writer.getPath()).isEqualTo("$.a[2]");
writer.value(false);
assertThat(writer.getPath()).isEqualTo("$.a[3]");
writer.nullValue();
assertThat(writer.getPath()).isEqualTo("$.a[4]");
writer.value("b");
assertThat(writer.getPath()).isEqualTo("$.a[5]");
writer.beginObject();
assertThat(writer.getPath()).isEqualTo("$.a[5].");
writer.name("c");
assertThat(writer.getPath()).isEqualTo("$.a[5].c");
writer.value("d");
assertThat(writer.getPath()).isEqualTo("$.a[5].c");
writer.endObject();
assertThat(writer.getPath()).isEqualTo("$.a[6]");
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$.a[6][0]");
writer.value(3);
assertThat(writer.getPath()).isEqualTo("$.a[6][1]");
writer.endArray();
assertThat(writer.getPath()).isEqualTo("$.a[7]");
writer.endArray();
assertThat(writer.getPath()).isEqualTo("$.a");
writer.endObject();
assertThat(writer.getPath()).isEqualTo("$");
}
@Test public void arrayOfObjects() throws IOException {
JsonWriter writer = new JsonWriter(new Buffer());
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$[0]");
writer.beginObject();
assertThat(writer.getPath()).isEqualTo("$[0].");
writer.endObject();
assertThat(writer.getPath()).isEqualTo("$[1]");
writer.beginObject();
assertThat(writer.getPath()).isEqualTo("$[1].");
writer.endObject();
assertThat(writer.getPath()).isEqualTo("$[2]");
writer.beginObject();
assertThat(writer.getPath()).isEqualTo("$[2].");
writer.endObject();
assertThat(writer.getPath()).isEqualTo("$[3]");
writer.endArray();
assertThat(writer.getPath()).isEqualTo("$");
}
@Test public void arrayOfArrays() throws IOException {
JsonWriter writer = new JsonWriter(new Buffer());
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$[0]");
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$[0][0]");
writer.endArray();
assertThat(writer.getPath()).isEqualTo("$[1]");
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$[1][0]");
writer.endArray();
assertThat(writer.getPath()).isEqualTo("$[2]");
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$[2][0]");
writer.endArray();
assertThat(writer.getPath()).isEqualTo("$[3]");
writer.endArray();
assertThat(writer.getPath()).isEqualTo("$");
}
@Test public void objectPath() throws IOException {
JsonWriter writer = new JsonWriter(new Buffer());
assertThat(writer.getPath()).isEqualTo("$");
writer.beginObject();
assertThat(writer.getPath()).isEqualTo("$.");
writer.name("a");
assertThat(writer.getPath()).isEqualTo("$.a");
writer.value(1);
assertThat(writer.getPath()).isEqualTo("$.a");
writer.name("b");
assertThat(writer.getPath()).isEqualTo("$.b");
writer.value(2);
assertThat(writer.getPath()).isEqualTo("$.b");
writer.endObject();
assertThat(writer.getPath()).isEqualTo("$");
writer.close();
assertThat(writer.getPath()).isEqualTo("$");
}
@Test public void nestedObjects() throws IOException {
JsonWriter writer = new JsonWriter(new Buffer());
assertThat(writer.getPath()).isEqualTo("$");
writer.beginObject();
assertThat(writer.getPath()).isEqualTo("$.");
writer.name("a");
assertThat(writer.getPath()).isEqualTo("$.a");
writer.beginObject();
assertThat(writer.getPath()).isEqualTo("$.a.");
writer.name("b");
assertThat(writer.getPath()).isEqualTo("$.a.b");
writer.beginObject();
assertThat(writer.getPath()).isEqualTo("$.a.b.");
writer.name("c");
assertThat(writer.getPath()).isEqualTo("$.a.b.c");
writer.nullValue();
assertThat(writer.getPath()).isEqualTo("$.a.b.c");
writer.endObject();
assertThat(writer.getPath()).isEqualTo("$.a.b");
writer.endObject();
assertThat(writer.getPath()).isEqualTo("$.a");
writer.endObject();
assertThat(writer.getPath()).isEqualTo("$");
}
@Test public void arrayPath() throws IOException {
JsonWriter writer = new JsonWriter(new Buffer());
assertThat(writer.getPath()).isEqualTo("$");
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$[0]");
writer.value(1);
assertThat(writer.getPath()).isEqualTo("$[1]");
writer.value(true);
assertThat(writer.getPath()).isEqualTo("$[2]");
writer.value("a");
assertThat(writer.getPath()).isEqualTo("$[3]");
writer.value(5.5d);
assertThat(writer.getPath()).isEqualTo("$[4]");
writer.value(BigInteger.ONE);
assertThat(writer.getPath()).isEqualTo("$[5]");
writer.endArray();
assertThat(writer.getPath()).isEqualTo("$");
writer.close();
assertThat(writer.getPath()).isEqualTo("$");
}
@Test public void nestedArrays() throws IOException {
JsonWriter writer = new JsonWriter(new Buffer());
assertThat(writer.getPath()).isEqualTo("$");
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$[0]");
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$[0][0]");
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$[0][0][0]");
writer.nullValue();
assertThat(writer.getPath()).isEqualTo("$[0][0][1]");
writer.endArray();
assertThat(writer.getPath()).isEqualTo("$[0][1]");
writer.endArray();
assertThat(writer.getPath()).isEqualTo("$[1]");
writer.endArray();
assertThat(writer.getPath()).isEqualTo("$");
writer.close();
assertThat(writer.getPath()).isEqualTo("$");
}
@Test public void multipleTopLevelValuesInOneDocument() throws IOException {
JsonWriter writer = new JsonWriter(new Buffer());
writer.setLenient(true);
writer.beginArray();
writer.endArray();
assertThat(writer.getPath()).isEqualTo("$");
writer.beginArray();
writer.endArray();
assertThat(writer.getPath()).isEqualTo("$");
}
@Test public void skipNulls() throws IOException {
JsonWriter writer = new JsonWriter(new Buffer());
writer.setSerializeNulls(false);
assertThat(writer.getPath()).isEqualTo("$");
writer.beginObject();
assertThat(writer.getPath()).isEqualTo("$.");
writer.name("a");
assertThat(writer.getPath()).isEqualTo("$.a");
writer.nullValue();
assertThat(writer.getPath()).isEqualTo("$.a");
writer.name("b");
assertThat(writer.getPath()).isEqualTo("$.b");
writer.nullValue();
assertThat(writer.getPath()).isEqualTo("$.b");
writer.endObject();
assertThat(writer.getPath()).isEqualTo("$");
}
}