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

ca.gc.aafc.dina.jsonapi.JSONApiDocumentStructure Maven / Gradle / Ivy

There is a newer version: 0.134
Show newest version
package ca.gc.aafc.dina.jsonapi;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.databind.JsonNode;

import org.apache.commons.lang3.StringUtils;

/**
 * Collections of constants and utilities for JSON:API related code.
 */
public final class JSONApiDocumentStructure {

  // Utility class
  private JSONApiDocumentStructure() {
  }

  public static final String DATA = "data";
  public static final String INCLUDED = "included";
  public static final String META = "meta";

  public static final String ATTRIBUTES = "attributes";
  public static final String RELATIONSHIPS = "relationships";
  public static final String ID = "id";
  public static final String TYPE = "type";

  // path using dot notation (e.g. data.attributes)
  public static final String DATA_ATTRIBUTES_PATH = DATA + "." + ATTRIBUTES;
  public static final String DATA_RELATIONSHIPS_PATH = DATA + "." + RELATIONSHIPS;

  // JSON Pointer version
  public static final JsonPointer DATA_PTR = JsonPointer.valueOf("/" + DATA);
  public static final JsonPointer INCLUDED_PTR = JsonPointer.valueOf("/" + INCLUDED);
  public static final JsonPointer META_PTR = JsonPointer.valueOf("/" + META);
  public static final JsonPointer ATTRIBUTES_PTR = JsonPointer.valueOf("/" + DATA + "/" + ATTRIBUTES);
  public static final JsonPointer RELATIONSHIP_PTR = JsonPointer.valueOf("/" + DATA + "/" + RELATIONSHIPS);


  /**
   * Checks if the provided path refers to a relationships' path (data.relationships).
   * @param currentPath path using dot notation
   * @return
   */
  public static boolean isRelationshipsPath(String currentPath) {
    if(StringUtils.isBlank(currentPath)) {
      return false;
    }
    return currentPath.startsWith(DATA_RELATIONSHIPS_PATH);
  }

  /**
   * Checks if the provided path refers to an attribute' path (data.attributes).
   * @param currentPath path using dot notation
   * @return
   */
  public static boolean isAttributesPath(String currentPath) {
    if(StringUtils.isBlank(currentPath)) {
      return false;
    }
    return currentPath.startsWith(DATA_ATTRIBUTES_PATH);
  }

  /**
   * Returns a document's part represented by the JsonPointer or Optional.empty if not found.
   * @param document
   * @param ptr
   * @return
   */
  public static Optional atJsonPtr(JsonNode document, JsonPointer ptr) {
    JsonNode node = document.at(ptr);
    return node.isMissingNode() ? Optional.empty() : Optional.of(node);
  }


  /**
   * If the value of a map entry from the provided map is another map, merge it using dot notation.
   * Currently, limited to 1 level.
   * Given:
   *   "attribute1": "val1",
   *   "attribute2": {
   *     "nested1": "val nested 1"
   *   }
   *
   * Will output:
   *   "attribute1": "val1",
   *   "attribute2.nested1": "val nested 1"
   * @param theMap
   * @return
   */
  public static Map mergeNestedMapUsingDotNotation(Map theMap) {
    Map newMap = new HashMap<>();
    for (var entry : theMap.entrySet()) {
      if(entry.getValue() instanceof Map entryAsMap) {
        for (var b : entryAsMap.entrySet()) {
          newMap.put(entry.getKey() + "." + b.getKey(), b.getValue());
        }
      } else {
        // keep it as is
        newMap.put(entry.getKey(), entry.getValue());
      }
    }
    return newMap;
  }

  /**
   * If the value of a map entry from the provided map is another map, extract it using dot notation.
   * This function will recursively extract nested maps.
   * See {@link #mergeNestedMapUsingDotNotation(Map)}
   * @param theMap
   * @return result of the extraction as {@link ExtractNestedMapResult}
   */
  public static ExtractNestedMapResult extractNestedMapUsingDotNotation(Map theMap) {
    Set keysUsed = new HashSet<>();
    Map newMap = new HashMap<>();

    for (var entry : theMap.entrySet()) {
      if (entry.getValue() instanceof Map entryAsMapInitialLevel) {
        keysUsed.add(entry.getKey());
        // Try to see if we have another map inside the current map
        ExtractNestedMapResult nextLevelResult = extractNestedMapUsingDotNotation(
          (Map) entryAsMapInitialLevel);

        // add elements under the new computed key except if the value is another map
        for (var initialMapElement : entryAsMapInitialLevel.entrySet()) {
          if(!nextLevelResult.isUsedKey(initialMapElement.getKey().toString())) {
            addToMapWithContext(entry.getKey(), initialMapElement.getKey().toString(), initialMapElement.getValue(), newMap);
          }
        }
        // add the elements of the next (deeper) map (if there are some)
        for(var nextLevelMapElement : nextLevelResult.nestedMapsMap().entrySet()) {
          addToMapWithContext(entry.getKey(),
            nextLevelMapElement.getKey(), nextLevelMapElement.getValue(), newMap);
        }
      }
    }
    return new ExtractNestedMapResult(newMap, keysUsed);
  }

  /**
   * Compute a new key with a context prefix to add a value to the provided map.
   * @param context
   * @param key
   * @param value
   * @param theMap
   */
  private static void addToMapWithContext(String context, String key, Object value, Map theMap) {
    theMap.put(context + "." + key, value);
  }

  /**
   * Removes the attributes prefix from the current path.
   * "data.attributes.name" -> "name"
   * @param currentPath
   * @return the current path without the data.attributes. prefix or the provided string is the prefix is not present
   */
  public static String removeAttributesPrefix(String currentPath) {
    return StringUtils.removeStart(
            StringUtils.removeStart(currentPath, DATA_ATTRIBUTES_PATH), ".");
  }

  /**
   * Represents the extraction of nested maps.
   * @param nestedMapsMap the map containing the nested maps where the key is using the dot notation
   * @param usedKeys keys that were representing a map before getting extracted into nestedMapsMap
   */
  public record ExtractNestedMapResult(Map nestedMapsMap, Set usedKeys) {

    public boolean isUsedKey(String key) {
      if (usedKeys == null || key == null) {
        return false;
      }
      return usedKeys.contains(key);
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy