docs: private final Object parseValue(
        Field fieldContext,
        Type valueType,
        ArrayList<Type> context,
        Object destination,
        CustomizeJsonParser customizeParser,
        boolean handlePolymorphic)
        throws IOException {
  
      valueType = Data.resolveWildcardTypeOrTypeVariable(context, valueType);
      // resolve a parameterized type to a class
      Class<?> valueClass = valueType instanceof Class<?> ? (Class<?>) valueType : null;
      if (valueType instanceof ParameterizedType) {
        valueClass = Types.getRawClass((ParameterizedType) valueType);
      }
      // Void means skip
      if (valueClass == Void.class) {
        skipChildren();
        return null;
      }
      // value type is now null, class, parameterized type, or generic array type
      JsonToken token = getCurrentToken();
      try {
        switch (getCurrentToken()) {
          case START_ARRAY:
          case END_ARRAY:
            boolean isArray = Types.isArray(valueType);
            Preconditions.checkArgument(
                valueType == null
                    || isArray
                    || valueClass != null && Types.isAssignableToOrFrom(valueClass, Collection.class),
                "expected collection or array type but got %s",
                valueType);
            Collection<Object> collectionValue = null;
            if (customizeParser != null && fieldContext != null) {
              collectionValue = customizeParser.newInstanceForArray(destination, fieldContext);
            }
            if (collectionValue == null) {
              collectionValue = Data.newCollectionInstance(valueType);
            }
            Type subType = null;
            if (isArray) {
              subType = Types.getArrayComponentType(valueType);
            } else if (valueClass != null && Iterable.class.isAssignableFrom(valueClass)) {
              subType = Types.getIterableParameter(valueType);
            }
            subType = Data.resolveWildcardTypeOrTypeVariable(context, subType);
            parseArray(fieldContext, collectionValue, subType, context, customizeParser);
            if (isArray) {
              return Types.toArray(collectionValue, Types.getRawArrayComponentType(context, subType));
            }
            return collectionValue;
          case FIELD_NAME:
          case START_OBJECT:
          case END_OBJECT:
            Preconditions.checkArgument(
                !Types.isArray(valueType), "expected object or map type but got %s", valueType);
            // Check if we're parsing into a polymorphic datatype.
            Field typemapField = handlePolymorphic ? getCachedTypemapFieldFor(valueClass) : null;
            Object newInstance = null;
            if (valueClass != null && customizeParser != null) {
              newInstance = customizeParser.newInstanceForObject(destination, valueClass);
            }
            boolean isMap = valueClass != null && Types.isAssignableToOrFrom(valueClass, Map.class);
            if (typemapField != null) {
              newInstance = new GenericJson();
            } else if (newInstance == null) {
              // check if it is a map to avoid ClassCastException to Map
              if (isMap || valueClass == null) {
                newInstance = Data.newMapInstance(valueClass);
              } else {
                newInstance = Types.newInstance(valueClass);
              }
            }
            int contextSize = context.size();
            if (valueType != null) {
              context.add(valueType);
            }
            if (isMap && !GenericData.class.isAssignableFrom(valueClass)) {
              Type subValueType =
                  Map.class.isAssignableFrom(valueClass)
                      ? Types.getMapValueParameter(valueType)
                      : null;
              if (subValueType != null) {
                @SuppressWarnings("unchecked")
                Map<String, Object> destinationMap = (Map<String, Object>) newInstance;
                parseMap(fieldContext, destinationMap, subValueType, context, customizeParser);
                return newInstance;
              }
            }
            parse(context, newInstance, customizeParser);
            if (valueType != null) {
              context.remove(contextSize);
            }
            if (typemapField == null) {
              return newInstance;
            }
  
            // Get the correct type out of the naively parsed data.
            Object typeValueObject = ((GenericJson) newInstance).get(typemapField.getName());
            Preconditions.checkArgument(
                typeValueObject != null, "No value specified for @JsonPolymorphicTypeMap field");
            String typeValue = typeValueObject.toString();
            JsonPolymorphicTypeMap typeMap = typemapField.getAnnotation(JsonPolymorphicTypeMap.class);
            Class<?> typeClass = null;
            for (TypeDef typeDefinition : typeMap.typeDefinitions()) {
              if (typeDefinition.key().equals(typeValue)) {
                typeClass = typeDefinition.ref();
                break;
              }
            }
            Preconditions.checkArgument(
                typeClass != null, "No TypeDef annotation found with key: " + typeValue);
            JsonFactory factory = getFactory();
            // TODO(ngmiceli): Avoid having to parse JSON content twice. Optimize when type is first.
            JsonParser parser = factory.createJsonParser(factory.toString(newInstance));
            parser.startParsing();
            return parser.parseValue(fieldContext, typeClass, context, null, null, false);
          case VALUE_TRUE:
          case VALUE_FALSE:
            Preconditions.checkArgument(
                valueType == null
                    || valueClass == boolean.class
                    || valueClass != null && valueClass.isAssignableFrom(Boolean.class),
                "expected type Boolean or boolean but got %s",
                valueType);
            return token == JsonToken.VALUE_TRUE ? Boolean.TRUE : Boolean.FALSE;
          case VALUE_NUMBER_FLOAT:
          case VALUE_NUMBER_INT:
            Preconditions.checkArgument(
                fieldContext == null || fieldContext.getAnnotation(JsonString.class) == null,
                "number type formatted as a JSON number cannot use @JsonString annotation");
            if (getCurrentToken() == JsonToken.VALUE_NUMBER_INT
                && (valueClass == null || valueClass.isAssignableFrom(Integer.class))) {
              return getIntValue();
            }
            if (valueClass == null || valueClass.isAssignableFrom(BigDecimal.class)) {
              return getDecimalValue();
            }
            if (valueClass == BigInteger.class) {
              return getBigIntegerValue();
            }
            if (valueClass == Double.class || valueClass == double.class) {
              return getDoubleValue();
            }
            if (valueClass == Long.class || valueClass == long.class) {
              return getLongValue();
            }
            if (valueClass == Float.class || valueClass == float.class) {
              return getFloatValue();
            }
            if (valueClass == Integer.class || valueClass == int.class) {
              return getIntValue();
            }
            if (valueClass == Short.class || valueClass == short.class) {
              return getShortValue();
            }
            if (valueClass == Byte.class || valueClass == byte.class) {
              return getByteValue();
            }
            throw new IllegalArgumentException("expected numeric type but got " + valueType);
          case VALUE_STRING:
            // TODO(user): Maybe refactor this method in multiple mini-methods for readability?
            String text = getText().trim().toLowerCase(Locale.US);
            // If we are expecting a Float / Double and the Text is NaN (case insensitive)
            // Then: Accept, even if the Annotation is JsonString.
            // Otherwise: Check that the Annotation is not JsonString.
            if (!(((valueClass == float.class || valueClass == Float.class)
                    || (valueClass == double.class || valueClass == Double.class))
                && (text.equals("nan") || text.equals("infinity") || text.equals("-infinity")))) {
              Preconditions.checkArgument(
                  valueClass == null
                      || !Number.class.isAssignableFrom(valueClass)
                      || fieldContext != null && fieldContext.getAnnotation(JsonString.class) != null,
                  "number field formatted as a JSON string must use the @JsonString annotation");
            }
            return Data.parsePrimitiveValue(valueType, getText());
          case VALUE_NULL:
            Preconditions.checkArgument(
                valueClass == null || !valueClass.isPrimitive(),
                "primitive number field but found a JSON null");
            if (valueClass != null
                && 0 != (valueClass.getModifiers() & (Modifier.ABSTRACT | Modifier.INTERFACE))) {
              if (Types.isAssignableToOrFrom(valueClass, Collection.class)) {
                return Data.nullOf(Data.newCollectionInstance(valueType).getClass());
              }
              if (Types.isAssignableToOrFrom(valueClass, Map.class)) {
                return Data.nullOf(Data.newMapInstance(valueClass).getClass());
              }
            }
            return Data.nullOf(Types.getRawArrayComponentType(context, valueType));
          default:
            throw new IllegalArgumentException("unexpected JSON node type: " + token);
        }
      } catch (IllegalArgumentException e) {
        // build context string
        StringBuilder contextStringBuilder = new StringBuilder();
        String currentName = getCurrentName();
        if (currentName != null) {
          contextStringBuilder.append("key ").append(currentName);
        }
        if (fieldContext != null) {
          if (currentName != null) {
            contextStringBuilder.append(", ");
          }
          contextStringBuilder.append("field ").append(fieldContext);
        }
        throw new IllegalArgumentException(contextStringBuilder.toString(), e);
      }
    }