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

com.kjetland.jackson.jsonSchema.Utils Maven / Gradle / Ivy

package com.kjetland.jackson.jsonSchema;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.util.ClassUtil;
import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaInject;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;

/**
 *
 * @author alex
 */
@Slf4j
public final class Utils {
    
    private Utils() {}
    
    public static String extractMinimalClassnameId(JavaType baseType, JavaType child) {
        // code taken from Jackson's MinimalClassNameIdResolver constructor and method idFromValue
        
        String base = baseType.getRawClass().getName();
        int ix = base.lastIndexOf('.');
        
        String basePackagePrefix;
        if (ix < 0) // can this ever occur?
            basePackagePrefix = ".";
        else
            basePackagePrefix = base.substring(0, ix + 1);
        
        String n = child.getRawClass().getName();
        if (n.startsWith(basePackagePrefix)) { // note: we will leave the leading dot in there
            return n.substring(basePackagePrefix.length() - 1);
        } else {
            return n;
        }
    }
    
    
    public static void merge(JsonNode mainNode, JsonNode updateNode) {
        Iterator fieldNames = updateNode.fieldNames();
        while (fieldNames.hasNext()) {
            String fieldName = fieldNames.next();
            JsonNode jsonNode = mainNode.get(fieldName);
            // if field exists and is an embedded object
            if (jsonNode != null && jsonNode.isObject()) {
                merge(jsonNode, updateNode.get(fieldName));
            }
            else {
                if (mainNode instanceof ObjectNode) {
                    // Overwrite field
                    JsonNode value = updateNode.get(fieldName);
                    ((ObjectNode)mainNode).set(fieldName, value);
                }
            }
        }
    }
    

    public static void visit(JsonNode o, String path, BiConsumer f) {
        String[] parts = path.split(Pattern.quote("/"));
        String lastPart = parts[parts.length - 1];
        String[] otherParts = Arrays.copyOfRange(parts, 0, parts.length - 1);
        JsonNode p = o;
        for (String name : otherParts) {
            JsonNode child = p.get(name);
            if (child == null)
                child = ((ObjectNode)p).putObject(name);
            p = child;
        }
        f.accept((ObjectNode)p, lastPart);
    }

    public static String camelCaseToSentenceCase(String propertyName) {
        // Code found here: http://stackoverflow.com/questions/2559759/how-do-i-convert-camelcase-into-human-readable-names-in-java
        String s = propertyName.replaceAll(
            "(?<=[A-Z])(?=[A-Z][a-z])"
            + "|(?<=[^A-Z])(?=[A-Z])"
            + "|(?<=[A-Za-z])(?=[^A-Za-z])",
            " ");

        // Make the first letter uppercase
        return s.substring(0,1).toUpperCase() + s.substring(1);
    }

    public static JavaType resolveElementType(JavaType propertyType, BeanProperty prop, ObjectMapper objectMapper) {
        JavaType containedType = propertyType.containedType(0);
        if (containedType.getRawClass() == Object.class) {
            // Scala BS
            // https://github.com/FasterXML/jackson-module-scala/wiki/FAQ#deserializing-optionint-and-other-primitive-challenges
        	JsonDeserialize jsonDeserialize = prop.getAnnotation(JsonDeserialize.class);
            if (jsonDeserialize != null)
                return objectMapper.getTypeFactory().constructType(jsonDeserialize.contentAs());
            else {
                log.debug("Use @JsonDeserialize(contentAs=, keyAs=) to specify type of collection elements of {}", prop);
                return containedType;
            }
        } 
        else {
          // use containedType as is
          return containedType;
        }
    }
    
    public static ArrayNode getRequiredArrayNode(ObjectNode objectNode) {
        JsonNode requiredNode = objectNode.get("required");
        
        if (requiredNode == null) {
            requiredNode = JsonNodeFactory.instance.arrayNode();
            objectNode.set("required", requiredNode);
        }
        
        return (ArrayNode) requiredNode;
    }

    public static ObjectNode getOptionsNode(ObjectNode objectNode) {
        return getOrCreateObjectChild(objectNode, "options");
    }

    public static ObjectNode getOrCreateObjectChild(ObjectNode parentObjectNode, String name) {
        JsonNode childNode = parentObjectNode.get(name);
        
        if (childNode == null) {
            childNode = JsonNodeFactory.instance.objectNode();
            parentObjectNode.set(name, childNode);
        }
        
        return (ObjectNode) childNode;
    }
    

    public static String extractTypeName(JavaType type) {
        // use JsonTypeName annotation if present
    	JsonTypeName annotation = type.getRawClass().getDeclaredAnnotation(JsonTypeName.class);
        return Optional.ofNullable(annotation)
                .flatMap(a -> Optional.of(a.value()))
                .filter(a -> !a.isEmpty())
                .orElse(type.getRawClass().getSimpleName());
    }
    
    public static List> extractGroupsFromAnnotation(Annotation annotation) {
        // Annotations cannot implement interface, so we have to check each and every
        // javax-annotation... To prevent bugs with missing groups-extract-impl when new
        // validation-annotations are added, I've decided to do it using reflection
        Class annotationClass = annotation.annotationType();
        List> arr= new ArrayList<>();
        if (annotationClass.getPackage().getName().startsWith("javax.validation.constraints")) {
            try {
                Class[] groups = (Class[]) annotationClass.getMethod("groups").invoke(annotation);
                arr = Arrays.asList(groups);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e)  {
            }

        } 
        else if (annotation instanceof JsonSchemaInject)
        	arr=Arrays.asList(((JsonSchemaInject)annotation).javaxValidationGroups());

        return Collections.unmodifiableList(arr);
    }
    
    public static JavaType getSuperClass(JavaType type) {
        for (JavaType superType : ClassUtil.findSuperTypes(type, null, false))
            if (superType.getRawClass().isAnnotationPresent(JsonTypeInfo.class))
                return superType;
        // else
        return type.getSuperClass();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy