mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49:21 +08:00
Merge pull request #51 from square/jwilson_0609_jsonwriter_getpath
JsonWriter.getPath().
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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];
|
||||
|
||||
@@ -1267,30 +1259,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
219
moshi/src/test/java/com/squareup/moshi/JsonWriterPathTest.java
Normal file
219
moshi/src/test/java/com/squareup/moshi/JsonWriterPathTest.java
Normal 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("$");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user