private void push(Object newTop) { if (stackSize == stack.length) { if (stackSize == 256) { throw new JsonDataException("Nesting too deep at " + getPath()); } scopes = Arrays.copyOf(scopes, scopes.length * 2); pathNames = Arrays.copyOf(pathNames, pathNames.length * 2); pathIndices = Arrays.copyOf(pathIndices, pathIndices.length * 2); stack = Arrays.copyOf(stack, stack.length * 2); } stack[stackSize++] = newTop; }
final void pushScope(int newTop) { if (stackSize == scopes.length) { if (stackSize == 256) { throw new JsonDataException("Nesting too deep at " + getPath()); } scopes = Arrays.copyOf(scopes, scopes.length * 2); pathNames = Arrays.copyOf(pathNames, pathNames.length * 2); pathIndices = Arrays.copyOf(pathIndices, pathIndices.length * 2); } scopes[stackSize++] = newTop; }
final JsonDataException typeMismatch(@Nullable Object value, Object expected) { if (value == null) { return new JsonDataException( "Expected " + expected + " but was null at path " + getPath()); } else { return new JsonDataException("Expected " + expected + " but was " + value + ", a " + value.getClass().getName() + ", at path " + getPath()); } }
/** Before pushing a value on the stack this confirms that the stack has capacity. */ final boolean checkStack() { if (stackSize != scopes.length) return false; if (stackSize == 256) { throw new JsonDataException("Nesting too deep at " + getPath() + ": circular reference?"); } scopes = Arrays.copyOf(scopes, scopes.length * 2); pathNames = Arrays.copyOf(pathNames, pathNames.length * 2); pathIndices = Arrays.copyOf(pathIndices, pathIndices.length * 2); if (this instanceof JsonValueWriter) { ((JsonValueWriter) this).stack = Arrays.copyOf(((JsonValueWriter) this).stack, ((JsonValueWriter) this).stack.length * 2); } return true; }
@Override public Character fromJson(JsonReader reader) throws IOException { String value = reader.nextString(); if (value.length() > 1) { throw new JsonDataException( String.format(ERROR_FORMAT, "a char", '"' + value + '"', reader.getPath())); } return value.charAt(0); }
static int rangeCheckNextInt(JsonReader reader, String typeMessage, int min, int max) throws IOException { int value = reader.nextInt(); if (value < min || value > max) { throw new JsonDataException( String.format(ERROR_FORMAT, typeMessage, value, reader.getPath())); } return value; }
@Override public void toJson(JsonWriter writer, @Nullable T value) throws IOException { if (value == null) { throw new JsonDataException("Unexpected null at " + writer.getPath()); } else { delegate.toJson(writer, value); } } @Override boolean isLenient() {
@FromJson Card fromJson(String card) { if (card.length() != 2) throw new JsonDataException("Unknown card: " + card); char rank = card.charAt(0); switch (card.charAt(1)) { case 'C': return new Card(rank, Suit.CLUBS); case 'D': return new Card(rank, Suit.DIAMONDS); case 'H': return new Card(rank, Suit.HEARTS); case 'S': return new Card(rank, Suit.SPADES); default: throw new JsonDataException("unknown suit: " + card); } } }
@FromJson @CardString Card fromJson(String card) { if (card.length() != 2) throw new JsonDataException("Unknown card: " + card); char rank = card.charAt(0); switch (card.charAt(1)) { case 'C': return new Card(rank, Suit.CLUBS); case 'D': return new Card(rank, Suit.DIAMONDS); case 'H': return new Card(rank, Suit.HEARTS); case 'S': return new Card(rank, Suit.SPADES); default: throw new JsonDataException("unknown suit: " + card); } } }
@Override public @Nullable T fromJson(JsonReader reader) throws IOException { if (reader.peek() == JsonReader.Token.NULL) { throw new JsonDataException("Unexpected null at " + reader.getPath()); } else { return delegate.fromJson(reader); } } @Override public void toJson(JsonWriter writer, @Nullable T value) throws IOException {
@Override public T fromJson(JsonReader reader) throws IOException { int index = reader.selectString(options); if (index != -1) return constants[index]; // We can consume the string safely, we are terminating anyway. String path = reader.getPath(); String name = reader.nextString(); throw new JsonDataException("Expected one of " + Arrays.asList(nameStrings) + " but was " + name + " at path " + path); }
@Override public void skipName() throws IOException { if (failOnUnknown) { throw new JsonDataException("Cannot skip unexpected " + peek() + " at " + getPath()); } Map.Entry<?, ?> peeked = require(Map.Entry.class, Token.NAME); // Swap the Map.Entry for its value on the stack. stack[stackSize - 1] = peeked.getValue(); pathNames[stackSize - 2] = "null"; }
@Override public Float fromJson(JsonReader reader) throws IOException { float value = (float) reader.nextDouble(); // Double check for infinity after float conversion; many doubles > Float.MAX if (!reader.isLenient() && Float.isInfinite(value)) { throw new JsonDataException("JSON forbids NaN and infinities: " + value + " at path " + reader.getPath()); } return value; }
@Override public @Nullable <T> T nextNull() throws IOException { int p = peeked; if (p == PEEKED_NONE) { p = doPeek(); } if (p == PEEKED_NULL) { peeked = PEEKED_NONE; pathIndices[stackSize - 1]++; return null; } else { throw new JsonDataException("Expected null but was " + peek() + " at path " + getPath()); } }
@CheckReturnValue public final @Nullable T fromJson(String string) throws IOException { JsonReader reader = JsonReader.of(new Buffer().writeUtf8(string)); T result = fromJson(reader); if (!isLenient() && reader.peek() != JsonReader.Token.END_DOCUMENT) { throw new JsonDataException("JSON document was not fully consumed."); } return result; }
@Override public void toJson(JsonWriter writer, Map<K, V> map) throws IOException { writer.beginObject(); for (Map.Entry<K, V> entry : map.entrySet()) { if (entry.getKey() == null) { throw new JsonDataException("Map key is null at " + writer.getPath()); } writer.promoteValueToName(); keyAdapter.toJson(writer, entry.getKey()); valueAdapter.toJson(writer, entry.getValue()); } writer.endObject(); }
@Override public void endArray() throws IOException { int p = peeked; if (p == PEEKED_NONE) { p = doPeek(); } if (p == PEEKED_END_ARRAY) { stackSize--; pathIndices[stackSize - 1]++; peeked = PEEKED_NONE; } else { throw new JsonDataException("Expected END_ARRAY but was " + peek() + " at path " + getPath()); } }
@Override public T convert(ResponseBody value) throws IOException { BufferedSource source = value.source(); try { // Moshi has no document-level API so the responsibility of BOM skipping falls to whatever // is delegating to it. Since it's a UTF-8-only library as well we only honor the UTF-8 BOM. if (source.rangeEquals(0, UTF8_BOM)) { source.skip(UTF8_BOM.size()); } JsonReader reader = JsonReader.of(source); T result = adapter.fromJson(reader); if (reader.peek() != JsonReader.Token.END_DOCUMENT) { throw new JsonDataException("JSON document was not fully consumed."); } return result; } finally { value.close(); } } }
@Override public void beginArray() throws IOException { int p = peeked; if (p == PEEKED_NONE) { p = doPeek(); } if (p == PEEKED_BEGIN_ARRAY) { pushScope(JsonScope.EMPTY_ARRAY); pathIndices[stackSize - 1] = 0; peeked = PEEKED_NONE; } else { throw new JsonDataException("Expected BEGIN_ARRAY but was " + peek() + " at path " + getPath()); } }
@Override public void beginObject() throws IOException { int p = peeked; if (p == PEEKED_NONE) { p = doPeek(); } if (p == PEEKED_BEGIN_OBJECT) { pushScope(JsonScope.EMPTY_OBJECT); peeked = PEEKED_NONE; } else { throw new JsonDataException("Expected BEGIN_OBJECT but was " + peek() + " at path " + getPath()); } }