All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.conqat.engine.commons.util.JsonUtils Maven / Gradle / Ivy

There is a newer version: 2025.1.0
Show newest version
/*-------------------------------------------------------------------------+
|                                                                          |
| Copyright 2005-2011 the ConQAT Project                                   |
|                                                                          |
| 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 org.conqat.engine.commons.util;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Optional;

import org.conqat.engine.core.core.ConQATException;
import org.conqat.lib.commons.collections.CollectionUtils;
import org.conqat.lib.commons.string.StringUtils;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

/**
 * Utility code for dealing with JSON. This uses Jackson for serialization and
 * deserialization to JSON.
 */
public class JsonUtils {

	/**
	 * The shared jackson object mapper instance. Once configured the object is
	 * thread-safe.
	 */
	private static final ObjectMapper OBJECT_MAPPER;

	static {
		JsonFactory jsonFactory = JsonFactory.builder() //
				.enable(JsonReadFeature.ALLOW_TRAILING_COMMA) //
				.enable(JsonReadFeature.ALLOW_UNQUOTED_FIELD_NAMES) //
				.enable(JsonReadFeature.ALLOW_SINGLE_QUOTES) //
				.build();
		OBJECT_MAPPER = JsonMapper.builder(jsonFactory) //
				.addModule(new ColorSerializationModule()) //
				.addModule(new ListMapSerializationModule()) //
				.addModule(new GuavaModule()) //
				.addModule(new JavaTimeModule()) //
				// Should these visibilities ever change, please also update
				// TeamscaleApiResource::allowPrivateBeanParamFields to reflect the same change
				.visibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
				.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
				.serializationInclusion(JsonInclude.Include.NON_NULL)
				.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) //
				.build();
	}

	/**
	 * Serializes the given object to JSON.
	 */
	public static String serializeToJSON(Object object) {
		try {
			return OBJECT_MAPPER.writeValueAsString(object);
		} catch (JsonProcessingException e) {
			throw new JsonSerializationException(e);
		}
	}

	/**
	 * Serializes the given object to JSON as UTF-8 encoded byte array.
	 */
	public static byte[] serializeToJSONByteArray(Object object) {
		try {
			return OBJECT_MAPPER.writeValueAsBytes(object);
		} catch (JsonProcessingException e) {
			throw new JsonSerializationException(e);
		}
	}

	/** @see #OBJECT_MAPPER */
	public static ObjectMapper getObjectMapper() {
		return OBJECT_MAPPER;
	}

	/** Returns a {@link JavaType} of the given class. */
	public static  JavaType getJavaType(Class resultClass) {
		return OBJECT_MAPPER.constructType(resultClass);
	}

	/** Returns a {@link JavaType} of a list of the given class. */
	public static  JavaType getJavaListType(Class resultClass) {
		return OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, resultClass);
	}

	/** Returns a {@link JavaType} of a nested list of the given class. */
	public static  JavaType getNestedJavaListType(Class resultClass) {
		return OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class,
				OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, resultClass));
	}

	/**
	 * Wrapper exception for {@link JsonProcessingException} as the other is a
	 * checked exception and we don't want to check it everywhere.
	 */
	public static class JsonSerializationException extends RuntimeException {
		private JsonSerializationException(Throwable cause) {
			super(cause);
		}
	}

	/**
	 * Deserializes a JSON string.
	 *
	 * @throws ConQATException
	 *             if the input string could not be parsed as JSON or the class is
	 *             not constructible.
	 */
	public static  T deserializeFromJson(String json, Class expectedClass) throws ConQATException {
		return deserializeFromJson(json, getJavaType(expectedClass));
	}

	/**
	 * Deserializes a JSON string.
	 *
	 * @throws ConQATException
	 *             if the input string could not be parsed as JSON or the class is
	 *             not constructible.
	 */
	public static  T deserializeFromJson(String json, TypeReference expectedType) throws ConQATException {
		return deserializeFromJson(json, OBJECT_MAPPER.getTypeFactory().constructType(expectedType));
	}

	/**
	 * Deserializes a JSON string.
	 *
	 * @throws ConQATException
	 *             if the input string could not be parsed as JSON or the class is
	 *             not constructible.
	 */
	public static  T deserializeFromJson(String json, JavaType expectedType) throws ConQATException {
		return safeConvert(objectMapper -> objectMapper.readValue(json, expectedType));
	}

	/**
	 * Deserializes a JSON string into a {@link JsonNode}.
	 *
	 * @throws ConQATException
	 *             if the input string could not be parsed as JSON or the class is
	 *             not constructible.
	 */
	public static JsonNode deserializeFromJson(String json) throws ConQATException {
		return safeConvert(objectMapper -> objectMapper.readTree(json));
	}

	/**
	 * Deserializes a JSON string. Throws an error if the resulting object is null
	 * or contains an attribute that is null.
	 *
	 * @throws ConQATException
	 *             if the input string could not be parsed as JSON, or the class is
	 *             not constructible, or the resulting object or one of its
	 *             attributes would be null.
	 */
	public static  T deserializeFromJsonWithNullCheck(String json, Class expectedClass) throws ConQATException {
		T parsedObject = deserializeFromJson(json, expectedClass);
		return NullableFieldValidator.ensureAllFieldsNonNull(parsedObject, json);
	}

	/**
	 * Returns the json value or {@code null} for the given key, while trying 1. a
	 * lowercase version of the key (e.g. 'project'), and 2. a capitalized version
	 * of the lowercase key ('Project');
	 */
	public static Optional getWithCapitalizedOrLowercaseKey(JsonNode jsonNode, String key) {
		key = key.toLowerCase();
		if (jsonNode.has(key)) {
			return Optional.of(jsonNode.get(key));
		}
		return Optional.ofNullable(jsonNode.get(StringUtils.capitalize(key)));
	}

	/** Returns whether a string is a valid json or not. */
	public static boolean isValidJson(String json) {
		try {
			OBJECT_MAPPER.readTree(json);
			return true;
		} catch (IOException e) {
			return false;
		}
	}

	/** Reformats a JSON string to be pretty printed. */
	public static String prettyPrintJSON(String json) throws ConQATException {
		return safeConvert(objectMapper -> {
			JsonNode jsonInstance = objectMapper.readTree(json);
			return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonInstance);
		});
	}

	/** Reformats a JSON string to be pretty printed. */
	public static String serializeToJSONPrettyPrinted(Object object) throws ConQATException {
		return safeConvert(objectMapper -> objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object));
	}

	/**
	 * Safely converts json to an object by calling the given supplier and wrapping
	 * any thrown exceptions in {@link ConQATException}.
	 */
	private static  T safeConvert(CollectionUtils.FunctionWithException convertAction)
			throws ConQATException {
		try {
			return convertAction.apply(OBJECT_MAPPER);
		} catch (JsonProcessingException e) {
			throw new ConQATException("Input was invalid JSON.", e);
		} catch (Throwable t) {
			throw new ConQATException("Trouble during JSON processing: " + t.getMessage(), t);
		}
	}

	/**
	 * Wrapper exception for {@link JsonProcessingException} as the other is a
	 * checked exception and we don't want to check it everywhere.
	 */
	public static class JsonDeserializationException extends RuntimeException {
		public JsonDeserializationException(String message, Throwable cause) {
			super(message, cause);
		}
	}

	/**
	 * @return whether the given field is not serialized, i.e. skipped in JSON
	 *         responses to the client.
	 */
	public static boolean isNotSerialized(Field field) {

		int fieldModifiers = field.getModifiers();

		// Jackson excludes transient and static fields by default.
		if (Modifier.isStatic(fieldModifiers) || Modifier.isTransient(fieldModifiers)) {
			return true;
		}
		// JsonIgnore indicates to skip this field
		if (field.isAnnotationPresent(JsonIgnore.class)) {
			return true;
		}
		// This is either due to dynamically inserted fields created by Jacoco during
		// test execution or to anonymous inner classes
		return field.getName().contains("$");

	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy