Back to Repositories

Testing JSON Path Tracking Implementation in google/gson

This test suite validates the JSON path tracking functionality in the GSON library’s JsonReader implementation. It ensures accurate path reporting during JSON parsing operations, covering both string-based and object-based reader implementations.

Test Coverage Overview

The test suite provides comprehensive coverage of JSON path tracking across different structural scenarios. Key areas tested include:

  • Basic object and array path navigation
  • Nested structure path tracking
  • Path reporting during skip operations
  • Path accuracy during element transitions
  • Edge cases like multiple top-level values

Implementation Analysis

The testing approach uses parameterized JUnit tests to validate both string-based and object-based JsonReader implementations. The test suite employs a Factory pattern to abstract reader creation, allowing identical test cases to run against different reader implementations.

Each test method systematically verifies both the current and previous paths at various stages of JSON parsing.

Technical Details

  • Testing Framework: JUnit with Parameterized test runner
  • Assertion Library: Google Truth
  • Test Data: Manually constructed JSON strings
  • Key Classes: JsonReader, JsonTreeReader
  • Path Format: Dollar-notation (e.g. $.a[0].b)

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Comprehensive state verification at each step
  • Systematic coverage of edge cases
  • Clear test method naming and organization
  • Reusable test infrastructure through parameterization
  • Consistent assertion patterns

google/gson

gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java

            
/*
 * 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.google.gson.stream;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;

import com.google.gson.JsonElement;
import com.google.gson.Strictness;
import com.google.gson.internal.Streams;
import com.google.gson.internal.bind.JsonTreeReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@SuppressWarnings("resource")
@RunWith(Parameterized.class)
public class JsonReaderPathTest {
  @Parameterized.Parameters(name = "{0}")
  public static List<Object[]> parameters() {
    return Arrays.asList(
        new Object[] {Factory.STRING_READER}, new Object[] {Factory.OBJECT_READER});
  }

  @Parameterized.Parameter public Factory factory;

  @Test
  public void path() throws IOException {
    JsonReader reader = factory.create("{\"a\":[2,true,false,null,\"b\",{\"c\":\"d\"},[3]]}");
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
    reader.beginObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$.");
    assertThat(reader.getPath()).isEqualTo("$.");
    String unused1 = reader.nextName();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a");
    assertThat(reader.getPath()).isEqualTo("$.a");
    reader.beginArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a[0]");
    assertThat(reader.getPath()).isEqualTo("$.a[0]");
    int unused2 = reader.nextInt();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a[0]");
    assertThat(reader.getPath()).isEqualTo("$.a[1]");
    boolean unused3 = reader.nextBoolean();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a[1]");
    assertThat(reader.getPath()).isEqualTo("$.a[2]");
    boolean unused4 = reader.nextBoolean();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a[2]");
    assertThat(reader.getPath()).isEqualTo("$.a[3]");
    reader.nextNull();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a[3]");
    assertThat(reader.getPath()).isEqualTo("$.a[4]");
    String unused5 = reader.nextString();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a[4]");
    assertThat(reader.getPath()).isEqualTo("$.a[5]");
    reader.beginObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a[5].");
    assertThat(reader.getPath()).isEqualTo("$.a[5].");
    String unused6 = reader.nextName();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a[5].c");
    assertThat(reader.getPath()).isEqualTo("$.a[5].c");
    String unused7 = reader.nextString();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a[5].c");
    assertThat(reader.getPath()).isEqualTo("$.a[5].c");
    reader.endObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a[5]");
    assertThat(reader.getPath()).isEqualTo("$.a[6]");
    reader.beginArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a[6][0]");
    assertThat(reader.getPath()).isEqualTo("$.a[6][0]");
    int unused8 = reader.nextInt();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a[6][0]");
    assertThat(reader.getPath()).isEqualTo("$.a[6][1]");
    reader.endArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a[6]");
    assertThat(reader.getPath()).isEqualTo("$.a[7]");
    reader.endArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a");
    assertThat(reader.getPath()).isEqualTo("$.a");
    reader.endObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
  }

  @Test
  public void objectPath() throws IOException {
    JsonReader reader = factory.create("{\"a\":1,\"b\":2}");
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");

    JsonToken unused1 = reader.peek();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
    reader.beginObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$.");
    assertThat(reader.getPath()).isEqualTo("$.");

    JsonToken unused2 = reader.peek();
    assertThat(reader.getPreviousPath()).isEqualTo("$.");
    assertThat(reader.getPath()).isEqualTo("$.");
    String unused3 = reader.nextName();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a");
    assertThat(reader.getPath()).isEqualTo("$.a");

    JsonToken unused4 = reader.peek();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a");
    assertThat(reader.getPath()).isEqualTo("$.a");
    int unused5 = reader.nextInt();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a");
    assertThat(reader.getPath()).isEqualTo("$.a");

    JsonToken unused6 = reader.peek();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a");
    assertThat(reader.getPath()).isEqualTo("$.a");
    String unused7 = reader.nextName();
    assertThat(reader.getPreviousPath()).isEqualTo("$.b");
    assertThat(reader.getPath()).isEqualTo("$.b");

    JsonToken unused8 = reader.peek();
    assertThat(reader.getPreviousPath()).isEqualTo("$.b");
    assertThat(reader.getPath()).isEqualTo("$.b");
    int unused9 = reader.nextInt();
    assertThat(reader.getPreviousPath()).isEqualTo("$.b");
    assertThat(reader.getPath()).isEqualTo("$.b");

    JsonToken unused10 = reader.peek();
    assertThat(reader.getPreviousPath()).isEqualTo("$.b");
    assertThat(reader.getPath()).isEqualTo("$.b");
    reader.endObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");

    JsonToken unused11 = reader.peek();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
    reader.close();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
  }

  @Test
  public void arrayPath() throws IOException {
    JsonReader reader = factory.create("[1,2]");
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");

    JsonToken unused1 = reader.peek();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
    reader.beginArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$[0]");
    assertThat(reader.getPath()).isEqualTo("$[0]");

    JsonToken unused2 = reader.peek();
    assertThat(reader.getPreviousPath()).isEqualTo("$[0]");
    assertThat(reader.getPath()).isEqualTo("$[0]");
    int unused3 = reader.nextInt();
    assertThat(reader.getPreviousPath()).isEqualTo("$[0]");
    assertThat(reader.getPath()).isEqualTo("$[1]");

    JsonToken unused4 = reader.peek();
    assertThat(reader.getPreviousPath()).isEqualTo("$[0]");
    assertThat(reader.getPath()).isEqualTo("$[1]");
    int unused5 = reader.nextInt();
    assertThat(reader.getPreviousPath()).isEqualTo("$[1]");
    assertThat(reader.getPath()).isEqualTo("$[2]");

    JsonToken unused6 = reader.peek();
    assertThat(reader.getPreviousPath()).isEqualTo("$[1]");
    assertThat(reader.getPath()).isEqualTo("$[2]");
    reader.endArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");

    JsonToken unused7 = reader.peek();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
    reader.close();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
  }

  @Test
  public void multipleTopLevelValuesInOneDocument() throws IOException {
    assumeTrue(factory == Factory.STRING_READER);

    JsonReader reader = factory.create("[][]");
    reader.setStrictness(Strictness.LENIENT);
    reader.beginArray();
    reader.endArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
    reader.beginArray();
    reader.endArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
  }

  @Test
  public void skipArrayElements() throws IOException {
    JsonReader reader = factory.create("[1,2,3]");
    reader.beginArray();
    reader.skipValue();
    reader.skipValue();
    assertThat(reader.getPreviousPath()).isEqualTo("$[1]");
    assertThat(reader.getPath()).isEqualTo("$[2]");
  }

  @Test
  public void skipArrayEnd() throws IOException {
    JsonReader reader = factory.create("[[],1]");
    reader.beginArray();
    reader.beginArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$[0][0]");
    assertThat(reader.getPath()).isEqualTo("$[0][0]");
    reader.skipValue(); // skip end of array
    assertThat(reader.getPreviousPath()).isEqualTo("$[0]");
    assertThat(reader.getPath()).isEqualTo("$[1]");
  }

  @Test
  public void skipObjectNames() throws IOException {
    JsonReader reader = factory.create("{\"a\":[]}");
    reader.beginObject();
    reader.skipValue();
    assertThat(reader.getPreviousPath()).isEqualTo("$.<skipped>");
    assertThat(reader.getPath()).isEqualTo("$.<skipped>");

    reader.beginArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$.<skipped>[0]");
    assertThat(reader.getPath()).isEqualTo("$.<skipped>[0]");
  }

  @Test
  public void skipObjectValues() throws IOException {
    JsonReader reader = factory.create("{\"a\":1,\"b\":2}");
    reader.beginObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$.");
    assertThat(reader.getPath()).isEqualTo("$.");
    String unused1 = reader.nextName();
    reader.skipValue();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a");
    assertThat(reader.getPath()).isEqualTo("$.a");
    String unused2 = reader.nextName();
    assertThat(reader.getPreviousPath()).isEqualTo("$.b");
    assertThat(reader.getPath()).isEqualTo("$.b");
  }

  @Test
  public void skipObjectEnd() throws IOException {
    JsonReader reader = factory.create("{\"a\":{},\"b\":2}");
    reader.beginObject();
    String unused = reader.nextName();
    reader.beginObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a.");
    assertThat(reader.getPath()).isEqualTo("$.a.");
    reader.skipValue(); // skip end of object
    assertThat(reader.getPreviousPath()).isEqualTo("$.a");
    assertThat(reader.getPath()).isEqualTo("$.a");
  }

  @Test
  public void skipNestedStructures() throws IOException {
    JsonReader reader = factory.create("[[1,2,3],4]");
    reader.beginArray();
    reader.skipValue();
    assertThat(reader.getPreviousPath()).isEqualTo("$[0]");
    assertThat(reader.getPath()).isEqualTo("$[1]");
  }

  @Test
  public void skipEndOfDocument() throws IOException {
    JsonReader reader = factory.create("[]");
    reader.beginArray();
    reader.endArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
    reader.skipValue();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
    reader.skipValue();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
  }

  @Test
  public void arrayOfObjects() throws IOException {
    JsonReader reader = factory.create("[{},{},{}]");
    reader.beginArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$[0]");
    assertThat(reader.getPath()).isEqualTo("$[0]");
    reader.beginObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$[0].");
    assertThat(reader.getPath()).isEqualTo("$[0].");
    reader.endObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$[0]");
    assertThat(reader.getPath()).isEqualTo("$[1]");
    reader.beginObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$[1].");
    assertThat(reader.getPath()).isEqualTo("$[1].");
    reader.endObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$[1]");
    assertThat(reader.getPath()).isEqualTo("$[2]");
    reader.beginObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$[2].");
    assertThat(reader.getPath()).isEqualTo("$[2].");
    reader.endObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$[2]");
    assertThat(reader.getPath()).isEqualTo("$[3]");
    reader.endArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
  }

  @Test
  public void arrayOfArrays() throws IOException {
    JsonReader reader = factory.create("[[],[],[]]");
    reader.beginArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$[0]");
    assertThat(reader.getPath()).isEqualTo("$[0]");
    reader.beginArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$[0][0]");
    assertThat(reader.getPath()).isEqualTo("$[0][0]");
    reader.endArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$[0]");
    assertThat(reader.getPath()).isEqualTo("$[1]");
    reader.beginArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$[1][0]");
    assertThat(reader.getPath()).isEqualTo("$[1][0]");
    reader.endArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$[1]");
    assertThat(reader.getPath()).isEqualTo("$[2]");
    reader.beginArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$[2][0]");
    assertThat(reader.getPath()).isEqualTo("$[2][0]");
    reader.endArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$[2]");
    assertThat(reader.getPath()).isEqualTo("$[3]");
    reader.endArray();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
  }

  @Test
  public void objectOfObjects() throws IOException {
    JsonReader reader = factory.create("{\"a\":{\"a1\":1,\"a2\":2},\"b\":{\"b1\":1}}");
    reader.beginObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$.");
    assertThat(reader.getPath()).isEqualTo("$.");
    String unused1 = reader.nextName();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a");
    assertThat(reader.getPath()).isEqualTo("$.a");
    reader.beginObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a.");
    assertThat(reader.getPath()).isEqualTo("$.a.");
    String unused2 = reader.nextName();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a.a1");
    assertThat(reader.getPath()).isEqualTo("$.a.a1");
    int unused3 = reader.nextInt();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a.a1");
    assertThat(reader.getPath()).isEqualTo("$.a.a1");
    String unused4 = reader.nextName();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a.a2");
    assertThat(reader.getPath()).isEqualTo("$.a.a2");
    int unused5 = reader.nextInt();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a.a2");
    assertThat(reader.getPath()).isEqualTo("$.a.a2");
    reader.endObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$.a");
    assertThat(reader.getPath()).isEqualTo("$.a");
    String unused6 = reader.nextName();
    assertThat(reader.getPreviousPath()).isEqualTo("$.b");
    assertThat(reader.getPath()).isEqualTo("$.b");
    reader.beginObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$.b.");
    assertThat(reader.getPath()).isEqualTo("$.b.");
    String unused7 = reader.nextName();
    assertThat(reader.getPreviousPath()).isEqualTo("$.b.b1");
    assertThat(reader.getPath()).isEqualTo("$.b.b1");
    int unused8 = reader.nextInt();
    assertThat(reader.getPreviousPath()).isEqualTo("$.b.b1");
    assertThat(reader.getPath()).isEqualTo("$.b.b1");
    reader.endObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$.b");
    assertThat(reader.getPath()).isEqualTo("$.b");
    reader.endObject();
    assertThat(reader.getPreviousPath()).isEqualTo("$");
    assertThat(reader.getPath()).isEqualTo("$");
  }

  public enum Factory {
    STRING_READER {
      @Override
      public JsonReader create(String data) {
        return new JsonReader(new StringReader(data));
      }
    },
    OBJECT_READER {
      @Override
      public JsonReader create(String data) {
        JsonElement element = Streams.parse(new JsonReader(new StringReader(data)));
        return new JsonTreeReader(element);
      }
    };

    abstract JsonReader create(String data);
  }
}