com.bazaarvoice.jolt.utils.JoltUtils Maven / Gradle / Ivy
/*
* Copyright 2013 Bazaarvoice, 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.bazaarvoice.jolt.utils;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Handy utilities that do NOT depend on JsonUtil / Jackson live here
*/
public class JoltUtils {
/**
* Removes a key recursively from anywhere in a JSON document.
* NOTE: mutates its input.
*
* @param json the Jackson Object version of the JSON document
* (contents changed by this call)
* @param keyToRemove the key to remove from the document
*/
public static void removeRecursive( Object json, String keyToRemove ) {
if ( ( json == null ) || ( keyToRemove == null ) ) {
return;
}
if ( json instanceof Map ) {
Map jsonMap = cast(json);
// If this level of the tree has the key we are looking for, remove it
// Do the lookup instead of just the remove to avoid un-necessarily
// dying on ImmutableMaps.
if ( jsonMap.containsKey( keyToRemove ) ) {
jsonMap.remove( keyToRemove );
}
// regardless, recurse down the tree
for ( Object value : jsonMap.values() ) {
removeRecursive( value, keyToRemove );
}
}
if ( json instanceof List ) {
for ( Object value : (List) json ) {
removeRecursive( value, keyToRemove );
}
}
}
/**
* Navigate a JSON tree (made up of Maps and Lists) to "lookup" the value
* at a particular path.
*
* Example : given Json
*
* Object json =
* {
* "a" : {
* "b" : [ "x", "y", "z" ]
* }
* }
*
* navigate( json, "a", "b", 0 ) will return "x".
*
* It will traverse down the nested "a" and return the zeroth item of the "b" array.
*
* You will either get your data, or null.
*
* It should never throw an Exception; even if
* - you ask to index an array with a negative number
* - you ask to index an array wiht a number bigger than the array size
* - you ask to index a map that does not exist
* - your input data has objects in it other than Map, List, String, Number.
*
* @param source the source JSON object (Map, List, String, Number)
* @param paths varargs path you want to travel
* @return the object of Type at final destination
*/
public static T navigate( final Object source, final Object... paths ) {
Object destination = source;
for ( Object path : paths ) {
if ( path == null || destination == null ) {
return null;
}
if ( destination instanceof Map ) {
destination = ((Map) destination).get( path );
}
else if ( destination instanceof List ) {
if ( ! (path instanceof Integer) ) {
return null;
}
List destList = (List) destination;
int pathInt = (Integer) path;
if ( pathInt < 0 || pathInt >= destList.size() ) {
return null;
}
destination = destList.get( pathInt );
}
else {
// the input at this level is not a Map or List
// so return null
return null;
}
}
return cast(destination);
}
/**
* Navigate a JSON tree (made up of Maps and Lists) to "lookup" the value
* at a particular path.
*
* You will either get your data, or an exception will be thrown.
*
* This method should generally only be used in situations where you "know"
* that the navigate call will "always succeed".
*
* @param source the source JSON object (Map, List, String, Number)
* @param paths varargs path you want to travel
* @return the object of Type at final destination
* @throws UnsupportedOperationException if there was any problem walking the JSON tree structure
*/
public static T navigateStrict( final Object source, final Object... paths ) throws UnsupportedOperationException {
Object destination = source;
for ( Object path : paths ) {
if ( path == null ) {
throw new UnsupportedOperationException("path is null");
}
if ( destination == null ) {
throw new UnsupportedOperationException("source is null");
}
if ( destination instanceof Map ) {
Map temp = (Map) destination;
if (temp.containsKey( path ) ) {
// if we don't check for containsKey first, then the Map.get call
// would return null for keys that don't actually exist.
destination = ((Map) destination).get(path);
}
else {
throw new UnsupportedOperationException("no entry for '" + path + "' found while traversing the JSON");
}
}
else if ( destination instanceof List ) {
if ( ! (path instanceof Integer) ) {
throw new UnsupportedOperationException( "path '" + path + "' is trying to be used as an array index");
}
List destList = (List) destination;
int pathInt = (Integer) path;
if ( pathInt < 0 || pathInt > destList.size() ) {
throw new UnsupportedOperationException( "path '" + path + "' is negative or outside the range of the list");
}
destination = destList.get( pathInt );
}
else {
throw new UnsupportedOperationException("Navigation supports only Map and List source types and non-null String and Integer path types");
}
}
return cast(destination);
}
/**
* Navigate a JSON tree (made up of Maps and Lists) to "lookup" the value
* at a particular path, but will return the supplied default value if
* there are any problems.
*
* @param source the source JSON object (Map, List, String, Number)
* @param paths varargs path you want to travel
* @return the object of Type at final destination or defaultValue if non existent
*/
public static T navigateOrDefault( final T defaultValue, final Object source, final Object... paths ) {
Object destination = source;
for ( Object path : paths ) {
if(path == null || destination == null) {
return defaultValue;
}
if(destination instanceof Map) {
Map destinationMap = (Map) destination;
if(!destinationMap.containsKey(path)) {
return defaultValue;
}
else {
destination = destinationMap.get(path);
}
}
else if(path instanceof Integer && destination instanceof List) {
List destList = (List) destination;
int pathInt = (Integer) path;
if ( pathInt < 0 || pathInt >= destList.size() ) {
return defaultValue;
}
else {
destination = destList.get( pathInt );
}
}
else {
return defaultValue;
}
}
return cast(destination);
}
/**
* Use navigateOrDefault which is a much better name.
*/
@Deprecated
public static T navigateSafe(final T defaultValue, final Object source, final Object... paths) {
return navigateOrDefault( defaultValue, source, paths );
}
/**
* Vacant implies there are empty placeholders, i.e. a vacant hotel
* Given a json document, checks if it has any "leaf" values, can handle deep nesting of lists and maps
*
* i.e. { "a": [ "x": {}, "y": [] ], "b": { "p": [], "q": {} }} ==> is empty
*
* @param obj source
* @return true if its an empty json, can have deep nesting, false otherwise
*/
public static boolean isVacantJson(final Object obj) {
Collection values = null;
if(obj instanceof Collection) {
if(((Collection) obj).size() == 0) {
return true;
}
values = (Collection) obj;
}
if(obj instanceof Map) {
if(((Map) obj).size() == 0) {
return true;
}
values = ((Map) obj).values();
}
int processedEmpty = 0;
if(values != null) {
for (Object value: values) {
if(!isVacantJson(value)) {
return false;
}
processedEmpty++;
}
if(processedEmpty == values.size()) {
return true;
}
}
return false;
}
/**
* Given a json document checks if its jst blank doc, i.e. [] or {}
*
* @param obj source
* @return true if the json doc is [] or {}
*/
public static boolean isBlankJson(final Object obj) {
if (obj == null) {
return true;
}
if(obj instanceof Collection) {
return (((Collection) obj).size() == 0);
}
if(obj instanceof Map) {
return (((Map) obj).size() == 0);
}
throw new UnsupportedOperationException("map or list is supported, got ${obj?obj.getClass():null}");
}
/**
* Given a json document, finds out absolute path to every leaf element
*
* i.e. { "a": [ "x": { "y": "alpha" }], "b": { "p": [ "beta", "gamma" ], "q": {} }} will yield
*
* 1) "a",0,"x","y" -> to "alpha"
* 2) "b","p", 0 -> to "beta"
* 3) "b", "p", 1 -> to "gamma"
* 4) "b","q" -> to {} (empty Map)
*
* @param source json
* @return list of Object[] representing path to every leaf element
*/
public static List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy