graphql.nadel.enginekt.transform.result.json.JsonNodeExtractor.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nadel-engine-nextgen Show documentation
Show all versions of nadel-engine-nextgen Show documentation
Nadel is a Java library that combines multiple GrahpQL services together into one API.
The newest version!
package graphql.nadel.enginekt.transform.result.json
import graphql.nadel.enginekt.transform.query.NadelQueryPath
import graphql.nadel.enginekt.util.AnyList
import graphql.nadel.enginekt.util.AnyMap
import graphql.nadel.enginekt.util.JsonMap
import graphql.nadel.enginekt.util.foldWhileNotNull
@Deprecated("Start moving to JsonNodes for performance reasons")
object JsonNodeExtractor {
/**
* Extracts the nodes at the given query selection path.
*/
fun getNodesAt(data: JsonMap, queryPath: NadelQueryPath, flatten: Boolean = false): List {
val rootNode = JsonNode(JsonNodePath.root, data)
return getNodesAt(rootNode, queryPath, flatten)
}
/**
* Extracts the nodes at the given query selection path.
*/
fun getNodesAt(rootNode: JsonNode, queryPath: NadelQueryPath, flatten: Boolean = false): List {
// This is a breadth-first search
return queryPath.segments.foldIndexed(listOf(rootNode)) { index, queue, pathSegment ->
val atEnd = index == queryPath.segments.lastIndex
// For all the nodes, get the next node according to the segment value
// We use flatMap as one node may be a list with more than one node to explore
queue.flatMap { node ->
// At the end when we see lists we do NOT want to flatten them for BFS queue
getNodes(node, pathSegment, flattenLists = !atEnd || flatten)
}
}
}
/**
* Extract the node at the given json node path.
*/
fun getNodeAt(data: JsonMap, path: JsonNodePath): JsonNode? {
val rootNode = JsonNode(JsonNodePath.root, data)
return getNodeAt(rootNode, path)
}
/**
* Extract the node at the given json node path.
*/
fun getNodeAt(rootNode: JsonNode, path: JsonNodePath): JsonNode? {
return path.segments.foldWhileNotNull(rootNode as JsonNode?) { currentNode, segment ->
when (currentNode?.value) {
is AnyMap -> currentNode.value[segment.value]?.let {
JsonNode(currentNode.resultPath + segment, it)
}
is AnyList -> when (segment) {
is JsonNodePathSegment.Int -> currentNode.value.getOrNull(segment.value)?.let {
JsonNode(currentNode.resultPath + segment, it)
}
else -> null
}
else -> null
}
}
}
private fun getNodes(node: JsonNode, segment: String, flattenLists: Boolean): List {
return when (node.value) {
is AnyMap -> getNodes(node.resultPath, node.value, segment, flattenLists)
null -> emptyList()
else -> throw IllegalNodeTypeException(node)
}
}
private fun getNodes(
parentPath: JsonNodePath,
map: AnyMap,
segment: String,
flattenLists: Boolean,
): List {
val newPath = parentPath + segment
val value = map[segment]
// We flatten lists as these nodes contribute to the BFS queue
if (value is AnyList && flattenLists) {
return getFlatNodes(parentPath = newPath, value)
}
return listOf(JsonNode(newPath, value))
}
/**
* Collects [JsonMap] nodes inside a [List]. Effectively we call this function to remove
* traces of [List]s.
*
* For example for the path /users we return the following nodes:
*
* `/users/[0]`
*
* `/users/[1]`
*
* etc.
*/
private fun getFlatNodes(parentPath: JsonNodePath, values: AnyList): List {
return values.flatMapIndexed { index, value ->
val newPath = parentPath + index
when (value) {
is AnyList -> getFlatNodes(newPath, value)
else -> listOf(JsonNode(newPath, value))
}
}
}
}
class IllegalNodeTypeException private constructor(message: String) : RuntimeException(message) {
companion object {
operator fun invoke(node: JsonNode): IllegalNodeTypeException {
val nodeType = node.value?.javaClass?.name ?: "null"
val pathString = node.resultPath.segments.joinToString("/") {
it.value.toString()
}
return IllegalNodeTypeException("Unknown node type '$nodeType' at '$pathString' was not a map")
}
}
}