
com.google.api.client.json.JsonParser Maven / Gradle / Ivy
Show all versions of google-api-client Show documentation
/*
* Copyright (c) 2010 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.api.client.json;
import com.google.api.client.util.ClassInfo;
import com.google.api.client.util.Data;
import com.google.api.client.util.FieldInfo;
import com.google.api.client.util.GenericData;
import com.google.api.client.util.Types;
import com.google.common.base.Preconditions;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
/**
* Abstract low-level JSON parser.
*
* @since 1.3
* @author Yaniv Inbar
*/
public abstract class JsonParser {
/** Returns the JSON factory from which this generator was created. */
public abstract JsonFactory getFactory();
/**
* Closes the parser and the underlying input stream or reader, and releases any memory associated
* with it.
*
* @throws IOException if failed
*/
public abstract void close() throws IOException;
/** Returns the next token from the stream or {@code null} to indicate end of input. */
public abstract JsonToken nextToken() throws IOException;
/**
* Returns the token the parser currently points to or {@code null} for none (at start of input or
* after end of input).
*/
public abstract JsonToken getCurrentToken();
/**
* Returns the most recent field name or {@code null} for array values or for root-level values.
*/
public abstract String getCurrentName() throws IOException;
/**
* Skips to the matching {@link JsonToken#END_ARRAY} if current token is
* {@link JsonToken#START_ARRAY}, the matching {@link JsonToken#END_OBJECT} if the current token
* is {@link JsonToken#START_OBJECT}, else does nothing.
*/
public abstract JsonParser skipChildren() throws IOException;
/**
* Returns a textual representation of the current token or {@code null} if
* {@link #getCurrentToken()} is {@code null}.
*/
public abstract String getText() throws IOException;
// TODO(yanivi): Jackson provides getTextCharacters(), getTextLength(), and getTextOffset()
/** Returns the byte value of the current token. */
public abstract byte getByteValue() throws IOException;
/** Returns the short value of the current token. */
public abstract short getShortValue() throws IOException;
/** Returns the int value of the current token. */
public abstract int getIntValue() throws IOException;
/** Returns the float value of the current token. */
public abstract float getFloatValue() throws IOException;
/** Returns the long value of the current token. */
public abstract long getLongValue() throws IOException;
/** Returns the double value of the current token. */
public abstract double getDoubleValue() throws IOException;
/** Returns the {@link BigInteger} value of the current token. */
public abstract BigInteger getBigIntegerValue() throws IOException;
/** Returns the {@link BigDecimal} value of the current token. */
public abstract BigDecimal getDecimalValue() throws IOException;
/**
* Parse a JSON Object from the given JSON parser (which is closed after parsing completes) into
* the given destination class, optionally using the given parser customizer.
*
* @param destination class type
* @param destinationClass destination class that has a public default constructor to use to
* create a new instance
* @param customizeParser optional parser customizer or {@code null} for none
* @return new instance of the parsed destination class
*/
public final T parseAndClose(Class destinationClass, CustomizeJsonParser customizeParser)
throws IOException {
T newInstance = Types.newInstance(destinationClass);
parseAndClose(newInstance, customizeParser);
return newInstance;
}
/**
* Skips the values of all keys in the current object until it finds the given key.
*
* Before this method is called, the parser must either point to the start or end of a JSON object
* or to a field name. After this method ends, the current token will either be the
* {@link JsonToken#END_OBJECT} of the current object if the key is not found, or the value of the
* key that was found.
*
*
* @param keyToFind key to find
*/
public final void skipToKey(String keyToFind) throws IOException {
JsonToken curToken = startParsingObject();
while (curToken == JsonToken.FIELD_NAME) {
String key = getText();
nextToken();
if (keyToFind.equals(key)) {
break;
}
skipChildren();
curToken = nextToken();
}
}
/**
* Starts parsing an object by making sure the parser points to a field name or end of object.
*
* Before this method is called, the parser must either point to the start or end of a JSON object
* or to a field name. After the method is called, the current token must be either
* {@link JsonToken#FIELD_NAME} or {@link JsonToken#END_OBJECT}.
*
*/
private JsonToken startParsingObject() throws IOException {
JsonToken currentToken = getCurrentToken();
if (currentToken == JsonToken.START_OBJECT) {
currentToken = nextToken();
}
Preconditions.checkArgument(
currentToken == JsonToken.FIELD_NAME || currentToken == JsonToken.END_OBJECT, currentToken);
return currentToken;
}
/**
* Parse a JSON Object from the given JSON parser -- which is closed after parsing completes --
* into the given destination object, optionally using the given parser customizer.
*
* Before this method is called, the parser must either point to the start or end of a JSON object
* or to a field name.
*
*
* @param destination destination object
* @param customizeParser optional parser customizer or {@code null} for none
*/
public final void parseAndClose(Object destination, CustomizeJsonParser customizeParser)
throws IOException {
try {
parse(destination, customizeParser);
} finally {
close();
}
}
/**
* Parse a JSON Object from the given JSON parser into the given destination class, optionally
* using the given parser customizer.
*
* Before this method is called, the parser must either point to the start or end of a JSON object
* or to a field name. After this method ends, the current token will be the
* {@link JsonToken#END_OBJECT} of the current object.
*
*
* @param destination class type
* @param destinationClass destination class that has a public default constructor to use to
* create a new instance
* @param customizeParser optional parser customizer or {@code null} for none
* @return new instance of the parsed destination class
*/
public final T parse(Class destinationClass, CustomizeJsonParser customizeParser)
throws IOException {
T newInstance = Types.newInstance(destinationClass);
parse(newInstance, customizeParser);
return newInstance;
}
/**
* Parse a JSON Object from the given JSON parser into the given destination object, optionally
* using the given parser customizer.
*
* Before this method is called, the parser must either point to the start or end of a JSON object
* or to a field name. After this method ends, the current token will be the
* {@link JsonToken#END_OBJECT} of the current object.
*
*
* @param destination destination object
* @param customizeParser optional parser customizer or {@code null} for none
*/
public final void parse(Object destination, CustomizeJsonParser customizeParser)
throws IOException {
ArrayList context = new ArrayList();
context.add(destination.getClass());
parse(context, destination, customizeParser);
}
private void parse(
ArrayList context, Object destination, CustomizeJsonParser customizeParser)
throws IOException {
if (destination instanceof GenericJson) {
((GenericJson) destination).jsonFactory = getFactory();
}
JsonToken curToken = startParsingObject();
Class destinationClass = destination.getClass();
ClassInfo classInfo = ClassInfo.of(destinationClass);
boolean isGenericData = GenericData.class.isAssignableFrom(destinationClass);
if (!isGenericData && Map.class.isAssignableFrom(destinationClass)) {
@SuppressWarnings("unchecked")
Map destinationMap = (Map) destination;
parseMap(
destinationMap, Types.getMapValueParameter(destinationClass), context, customizeParser);
return;
}
while (curToken == JsonToken.FIELD_NAME) {
String key = getText();
curToken = nextToken();
// stop at items for feeds
if (customizeParser != null && customizeParser.stopAt(destination, key)) {
return;
}
// get the field from the type information
FieldInfo fieldInfo = classInfo.getFieldInfo(key);
if (fieldInfo != null) {
// skip final fields
if (fieldInfo.isFinal() && !fieldInfo.isPrimitive()) {
throw new IllegalArgumentException("final array/object fields are not supported");
}
Field field = fieldInfo.getField();
int contextSize = context.size();
context.add(field.getGenericType());
Object fieldValue = parseValue(curToken,
field,
fieldInfo.getGenericType(),
context,
destination,
customizeParser);
context.remove(contextSize);
fieldInfo.setValue(destination, fieldValue);
} else if (isGenericData) {
// store unknown field in generic JSON
GenericData object = (GenericData) destination;
object.set(key, parseValue(curToken, null, null, context, destination, customizeParser));
} else {
// unrecognized field, skip value
if (customizeParser != null) {
customizeParser.handleUnrecognizedKey(destination, key);
}
skipChildren();
}
curToken = nextToken();
}
}
/**
* Parse a JSON Array from the given JSON parser (which is closed after parsing completes) into
* the given destination collection, optionally using the given parser customizer.
*
* @param destinationCollectionClass class of destination collection (must have a public default
* constructor)
* @param destinationItemClass class of destination collection item (must have a public default
* constructor)
* @param customizeParser optional parser customizer or {@code null} for none
*/
public final Collection parseArrayAndClose(Class destinationCollectionClass,
Class destinationItemClass, CustomizeJsonParser customizeParser) throws IOException {
try {
return parseArray(destinationCollectionClass, destinationItemClass, customizeParser);
} finally {
close();
}
}
/**
* Parse a JSON Array from the given JSON parser (which is closed after parsing completes) into
* the given destination collection, optionally using the given parser customizer.
*
* @param destinationCollection destination collection
* @param destinationItemClass class of destination collection item (must have a public default
* constructor)
* @param customizeParser optional parser customizer or {@code null} for none
*/
public final void parseArrayAndClose(Collection destinationCollection,
Class destinationItemClass, CustomizeJsonParser customizeParser) throws IOException {
try {
parseArray(destinationCollection, destinationItemClass, customizeParser);
} finally {
close();
}
}
/**
* Parse a JSON Array from the given JSON parser into the given destination collection, optionally
* using the given parser customizer.
*
* @param destinationCollectionClass class of destination collection (must have a public default
* constructor)
* @param destinationItemClass class of destination collection item (must have a public default
* constructor)
* @param customizeParser optional parser customizer or {@code null} for none
*/
public final Collection parseArray(Class destinationCollectionClass,
Class destinationItemClass, CustomizeJsonParser customizeParser) throws IOException {
@SuppressWarnings("unchecked")
Collection destinationCollection =
(Collection) Data.newCollectionInstance(destinationCollectionClass);
parseArray(destinationCollection, destinationItemClass, customizeParser);
return destinationCollection;
}
/**
* Parse a JSON Array from the given JSON parser into the given destination collection, optionally
* using the given parser customizer.
*
* @param destinationCollection destination collection
* @param destinationItemClass class of destination collection item (must have a public default
* constructor)
* @param customizeParser optional parser customizer or {@code null} for none
*/
public final void parseArray(Collection destinationCollection,
Class destinationItemClass, CustomizeJsonParser customizeParser) throws IOException {
parseArray(destinationCollection, destinationItemClass, new ArrayList(), customizeParser);
}
/**
* Parse a JSON Array from the given JSON parser into the given destination collection, optionally
* using the given parser customizer.
*
* @param destinationCollection destination collection
* @param destinationItemType type of destination collection item
* @param customizeParser optional parser customizer or {@code null} for none
*/
private void parseArray(Collection destinationCollection, Type destinationItemType,
ArrayList context, CustomizeJsonParser customizeParser) throws IOException {
JsonToken listToken;
while ((listToken = nextToken()) != JsonToken.END_ARRAY) {
@SuppressWarnings("unchecked")
T parsedValue = (T) parseValue(listToken,
null,
destinationItemType,
context,
destinationCollection,
customizeParser);
destinationCollection.add(parsedValue);
}
}
private void parseMap(Map destinationMap, Type valueType, ArrayList context,
CustomizeJsonParser customizeParser) throws IOException {
JsonToken curToken = startParsingObject();
while (curToken == JsonToken.FIELD_NAME) {
String key = getText();
curToken = nextToken();
// stop at items for feeds
if (customizeParser != null && customizeParser.stopAt(destinationMap, key)) {
return;
}
Object value =
parseValue(curToken, null, valueType, context, destinationMap, customizeParser);
destinationMap.put(key, value);
curToken = nextToken();
}
}
/**
* Parse a value.
*
* @param token JSON token
* @param field field or {@code null} for none (e.g. into a map)
* @param valueType value type or {@code null} if not known (e.g. into a map)
* @param destination destination object instance
* @param customizeParser customize parser or {@code null} for none
* @return parsed value
*/
private final Object parseValue(JsonToken token,
Field field,
Type valueType,
ArrayList context,
Object destination,
CustomizeJsonParser customizeParser) 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);
}
// value type is now null, class, parameterized type, or generic array type
switch (token) {
case START_ARRAY:
boolean isArray = Types.isArray(valueType);
Preconditions.checkArgument(valueType == null || isArray || valueClass != null
&& Types.isAssignableToOrFrom(valueClass, Collection.class),
"%s: expected collection or array type but got %s for field %s", getCurrentName(),
valueType, field);
Collection