org.conqat.engine.commons.util.JsonUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of teamscale-commons Show documentation
Show all versions of teamscale-commons Show documentation
Provides common DTOs for Teamscale
/*
* Copyright (c) CQSE GmbH
*
* 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.awt.geom.Rectangle2D;
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.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) //
.enable(JsonReadFeature.ALLOW_JAVA_COMMENTS) //
.build();
OBJECT_MAPPER = JsonMapper.builder(jsonFactory) //
.addModule(new ColorSerializationModule()) //
.addModule(new PublicProjectIdSerializationModule()) //
.addModule(new InternalProjectIdSerializationModule()) //
.addModule(new IProjectIdSerializationModule()) //
.addModule(new ListMapSerializationModule()) //
.addModule(new SetMapSerializationModule()) //
.addModule(new GuavaModule()) //
.addModule(new JavaTimeModule()) //
.addMixIn(Rectangle2D.class, Rectangle2DJsonIgnore.class)
// 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();
}
/** Mixin interface to avoid endless loop when serializing Rectangle objects. */
public interface Rectangle2DJsonIgnore {
/** We need to ignore the bounds2d field as it references to itself. */
@JsonIgnore
String getBounds2D();
}
/**
* Serializes the given object to JSON.
*/
public static String serializeToJSON(Object object) {
try {
return OBJECT_MAPPER.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new JsonSerializationRuntimeException(e);
}
}
/** @see #OBJECT_MAPPER */
public static ObjectMapper getObjectMapper() {
return OBJECT_MAPPER;
}
/** Returns a {@link JavaType} of the given class. */
private 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 JsonSerializationRuntimeException extends RuntimeException {
/** Version used for serialization. */
private static final long serialVersionUID = 1;
public JsonSerializationRuntimeException(Throwable cause) {
super(cause);
}
}
/**
* Deserializes a JSON string.
*
* @throws JsonSerializationException
* 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 JsonSerializationException {
return deserializeFromJson(json, getJavaType(expectedClass));
}
/**
* Deserializes a JSON string.
*
* @throws JsonSerializationException
* 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 JsonSerializationException {
return deserializeFromJson(json, OBJECT_MAPPER.getTypeFactory().constructType(expectedType));
}
/**
* Deserializes a JSON string.
*
* @throws JsonSerializationException
* 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 JsonSerializationException {
return safeConvert(objectMapper -> objectMapper.readValue(json, expectedType));
}
/**
* Deserializes a JSON string into a {@link JsonNode}.
*
* @throws JsonSerializationException
* if the input string could not be parsed as JSON or the class is
* not constructible.
*/
public static JsonNode deserializeFromJson(String json) throws JsonSerializationException {
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 JsonSerializationException
* 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 JsonSerializationException {
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 JsonSerializationException {
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 JsonSerializationException {
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 JsonSerializationException}.
*/
private static T safeConvert(CollectionUtils.FunctionWithException convertAction)
throws JsonSerializationException {
try {
return convertAction.apply(OBJECT_MAPPER);
} catch (JsonProcessingException e) {
throw new JsonSerializationException("Input was invalid JSON.", e);
} catch (Throwable t) {
throw new JsonSerializationException("Trouble during JSON processing: " + t.getMessage(), t);
}
}
/**
* @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