diff options
author | Muthuramalingam, Brinda Santh <brindasanth@in.ibm.com> | 2019-04-04 17:11:01 -0400 |
---|---|---|
committer | Muthuramalingam, Brinda Santh <brindasanth@in.ibm.com> | 2019-04-04 17:11:01 -0400 |
commit | 7468861e162ab94ad62f0a4abd1466778b993e3d (patch) | |
tree | c66dd9717e3e98a1a5da9a7ae900ab0897c1277f /ms | |
parent | 0007063cc856483e36dd49718dea5dda80ed6809 (diff) |
Improve data type handling
Change-Id: I5ebcfcecdf1781e30be1ca929b4bf9e1526691a3
Issue-ID: CCSDK-1127
Signed-off-by: Muthuramalingam, Brinda Santh <brindasanth@in.ibm.com>
Diffstat (limited to 'ms')
4 files changed, 119 insertions, 83 deletions
diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/DatabaseResourceAssignmentProcessor.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/DatabaseResourceAssignmentProcessor.kt index f17257ccf..e8b61a8fe 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/DatabaseResourceAssignmentProcessor.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/DatabaseResourceAssignmentProcessor.kt @@ -43,7 +43,8 @@ import java.util.* */ @Service("${PREFIX_RESOURCE_RESOLUTION_PROCESSOR}source-processor-db") @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) -open class DatabaseResourceAssignmentProcessor(private val bluePrintDBLibPropertySevice: BluePrintDBLibPropertySevice, private val primaryDBLibGenericService: PrimaryDBLibGenericService) +open class DatabaseResourceAssignmentProcessor(private val bluePrintDBLibPropertySevice: BluePrintDBLibPropertySevice, + private val primaryDBLibGenericService: PrimaryDBLibGenericService) : ResourceAssignmentProcessor() { private val logger = LoggerFactory.getLogger(DatabaseResourceAssignmentProcessor::class.java) @@ -84,11 +85,17 @@ open class DatabaseResourceAssignmentProcessor(private val bluePrintDBLibPropert ?: throw BluePrintProcessorException("couldn't get resource dictionary definition for $dName") val resourceSource = resourceDefinition.sources[dSource] ?: throw BluePrintProcessorException("couldn't get resource definition $dName source($dSource)") - val resourceSourceProperties = checkNotNull(resourceSource.properties) { "failed to get source properties for $dName " } + val resourceSourceProperties = checkNotNull(resourceSource.properties) { + "failed to get source properties for $dName " + } val sourceProperties = JacksonUtils.getInstanceFromMap(resourceSourceProperties, DatabaseResourceSource::class.java) - val sql = checkNotNull(sourceProperties.query) { "failed to get request query for $dName under $dSource properties" } - val inputKeyMapping = checkNotNull(sourceProperties.inputKeyMapping) { "failed to get input-key-mappings for $dName under $dSource properties" } + val sql = checkNotNull(sourceProperties.query) { + "failed to get request query for $dName under $dSource properties" + } + val inputKeyMapping = checkNotNull(sourceProperties.inputKeyMapping) { + "failed to get input-key-mappings for $dName under $dSource properties" + } logger.info("$dSource dictionary information : ($sql), ($inputKeyMapping), (${sourceProperties.outputKeyMapping})") val jdbcTemplate = blueprintDBLibService(sourceProperties) @@ -129,7 +136,7 @@ open class DatabaseResourceAssignmentProcessor(private val bluePrintDBLibPropert logger.trace("Reference dictionary key (${it.key}) resulted in value ($expressionValue)") namedParameters[it.key] = expressionValue } - logger.info("Parameter information : ({})", namedParameters) + logger.info("Parameter information : ($namedParameters)") return namedParameters } @@ -139,7 +146,9 @@ open class DatabaseResourceAssignmentProcessor(private val bluePrintDBLibPropert val dSource = resourceAssignment.dictionarySource val type = nullToEmpty(resourceAssignment.property?.type) - val outputKeyMapping = checkNotNull(sourceProperties.outputKeyMapping) { "failed to get output-key-mappings for $dName under $dSource properties" } + val outputKeyMapping = checkNotNull(sourceProperties.outputKeyMapping) { + "failed to get output-key-mappings for $dName under $dSource properties" + } logger.info("Response processing type($type)") // Primitive Types @@ -150,8 +159,10 @@ open class DatabaseResourceAssignmentProcessor(private val bluePrintDBLibPropert ResourceAssignmentUtils.setResourceDataValue(resourceAssignment, raRuntimeService, dbColumnValue) } in BluePrintTypes.validCollectionTypes() -> { - val entrySchemaType = checkNotEmpty(resourceAssignment.property?.entrySchema?.type) { "Entry schema is not defined for dictionary ($dName) info" } - val arrayNode = JsonNodeFactory.instance.arrayNode() + val entrySchemaType = checkNotEmpty(resourceAssignment.property?.entrySchema?.type) { + "Entry schema is not defined for dictionary ($dName) info" + } + val arrayNode = JacksonUtils.objectMapper.createArrayNode() rows.forEach { if (entrySchemaType in BluePrintTypes.validPrimitiveTypes()) { val dbColumnValue = it[outputKeyMapping[dName]] @@ -172,9 +183,9 @@ open class DatabaseResourceAssignmentProcessor(private val bluePrintDBLibPropert ResourceAssignmentUtils.setResourceDataValue(resourceAssignment, raRuntimeService, arrayNode) } else -> { - // Complex Types + // Custom Simple Complex Types val row = rows[0] - val objectNode = JsonNodeFactory.instance.objectNode() + val objectNode = JacksonUtils.objectMapper.createObjectNode() for (mapping in outputKeyMapping.entries) { val dbColumnValue = checkNotNull(row[mapping.key]) val propertyTypeForDataType = ResourceAssignmentUtils.getPropertyType(raRuntimeService, type, mapping.key) diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/RestResourceResolutionProcessor.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/RestResourceResolutionProcessor.kt index d95d6b614..cd93852bd 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/RestResourceResolutionProcessor.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/processor/RestResourceResolutionProcessor.kt @@ -18,7 +18,6 @@ package org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.processor import com.fasterxml.jackson.databind.node.ArrayNode -import com.fasterxml.jackson.databind.node.JsonNodeFactory import com.fasterxml.jackson.databind.node.MissingNode import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.RestResourceSource @@ -61,15 +60,20 @@ open class RestResourceResolutionProcessor(private val blueprintRestLibPropertyS val dSource = resourceAssignment.dictionarySource val resourceDefinition = resourceDictionaries[dName] ?: throw BluePrintProcessorException("couldn't get resource dictionary definition for $dName") + val resourceSource = resourceDefinition.sources[dSource] ?: throw BluePrintProcessorException("couldn't get resource definition $dName source($dSource)") + val resourceSourceProperties = checkNotNull(resourceSource.properties) { "failed to get source properties for $dName " } + val sourceProperties = JacksonUtils.getInstanceFromMap(resourceSourceProperties, RestResourceSource::class.java) + val path = nullToEmpty(sourceProperties.path) - val inputKeyMapping = - checkNotNull(sourceProperties.inputKeyMapping) { "failed to get input-key-mappings for $dName under $dSource properties" } + val inputKeyMapping = checkNotNull(sourceProperties.inputKeyMapping) { + "failed to get input-key-mappings for $dName under $dSource properties" + } val resolvedInputKeyMapping = resolveInputKeyMappingVariables(inputKeyMapping) // Resolving content Variables @@ -116,12 +120,14 @@ open class RestResourceResolutionProcessor(private val blueprintRestLibPropertyS val type = nullToEmpty(resourceAssignment.property?.type) lateinit var entrySchemaType: String - val outputKeyMapping = - checkNotNull(sourceProperties.outputKeyMapping) { "failed to get output-key-mappings for $dName under $dSource properties" } + val outputKeyMapping = checkNotNull(sourceProperties.outputKeyMapping) { + "failed to get output-key-mappings for $dName under $dSource properties" + } logger.info("Response processing type($type)") - val responseNode = - checkNotNull(JacksonUtils.jsonNode(restResponse).at(path)) { "Failed to find path ($path) in response ($restResponse)" } + val responseNode = checkNotNull(JacksonUtils.jsonNode(restResponse).at(path)) { + "Failed to find path ($path) in response ($restResponse)" + } logger.info("populating value for output mapping ($outputKeyMapping), from json ($responseNode)") @@ -132,23 +138,28 @@ open class RestResourceResolutionProcessor(private val blueprintRestLibPropertyS } in BluePrintTypes.validCollectionTypes() -> { // Array Types - entrySchemaType = - checkNotEmpty(resourceAssignment.property?.entrySchema?.type) { "Entry schema is not defined for dictionary ($dName) info" } + entrySchemaType = checkNotEmpty(resourceAssignment.property?.entrySchema?.type) { + "Entry schema is not defined for dictionary ($dName) info" + } val arrayNode = responseNode as ArrayNode if (entrySchemaType !in BluePrintTypes.validPrimitiveTypes()) { + val responseArrayNode = responseNode.toList() for (responseSingleJsonNode in responseArrayNode) { - val arrayChildNode = JsonNodeFactory.instance.objectNode() + + val arrayChildNode = JacksonUtils.objectMapper.createObjectNode() + outputKeyMapping.map { val responseKeyValue = responseSingleJsonNode.get(it.key) - val propertyTypeForDataType = - ResourceAssignmentUtils.getPropertyType(raRuntimeService, entrySchemaType, it.key) - logger.info("For List Type Resource: key (${it.key}), value ($responseKeyValue), type ({$propertyTypeForDataType})") + val propertyTypeForDataType = ResourceAssignmentUtils + .getPropertyType(raRuntimeService, entrySchemaType, it.key) + + logger.info("For List Type Resource: key (${it.key}), value ($responseKeyValue), " + + "type ({$propertyTypeForDataType})") + JacksonUtils.populateJsonNodeValues(it.value, - responseKeyValue, - propertyTypeForDataType, - arrayChildNode) + responseKeyValue, propertyTypeForDataType, arrayChildNode) } arrayNode.add(arrayChildNode) } @@ -159,13 +170,15 @@ open class RestResourceResolutionProcessor(private val blueprintRestLibPropertyS } else -> { // Complex Types - entrySchemaType = - checkNotEmpty(resourceAssignment.property?.type) { "Entry schema is not defined for dictionary ($dName) info" } - val objectNode = JsonNodeFactory.instance.objectNode() + entrySchemaType = checkNotEmpty(resourceAssignment.property?.type) { + "Entry schema is not defined for dictionary ($dName) info" + } + val objectNode = JacksonUtils.objectMapper.createObjectNode() outputKeyMapping.map { val responseKeyValue = responseNode.get(it.key) - val propertyTypeForDataType = - ResourceAssignmentUtils.getPropertyType(raRuntimeService, entrySchemaType, it.key) + val propertyTypeForDataType = ResourceAssignmentUtils + .getPropertyType(raRuntimeService, entrySchemaType, it.key) + logger.info("For List Type Resource: key (${it.key}), value ($responseKeyValue), type ({$propertyTypeForDataType})") JacksonUtils.populateJsonNodeValues(it.value, responseKeyValue, propertyTypeForDataType, objectNode) } diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/utils/ResourceAssignmentUtils.kt b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/utils/ResourceAssignmentUtils.kt index 86440e691..656e86169 100644 --- a/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/utils/ResourceAssignmentUtils.kt +++ b/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/utils/ResourceAssignmentUtils.kt @@ -26,7 +26,6 @@ import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.Reso import org.onap.ccsdk.cds.controllerblueprints.core.* import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintRuntimeService import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonReactorUtils -import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceDefinition import org.slf4j.LoggerFactory @@ -44,57 +43,53 @@ class ResourceAssignmentUtils { return JacksonReactorUtils.getMapFromFile(dictionaryFile, ResourceDefinition::class.java) } - // TODO("Modify Value type from Any to JsonNode") @Throws(BluePrintProcessorException::class) fun setResourceDataValue(resourceAssignment: ResourceAssignment, raRuntimeService: ResourceAssignmentRuntimeService, value: Any?) { + // TODO("See if Validation is needed in future with respect to conversion and Types") + return setResourceDataValue(resourceAssignment, raRuntimeService, value.asJsonType()) + } - val resourceProp = checkNotNull(resourceAssignment.property) { "Failed in setting resource value for resource mapping $resourceAssignment" } + @Throws(BluePrintProcessorException::class) + fun setResourceDataValue(resourceAssignment: ResourceAssignment, + raRuntimeService: ResourceAssignmentRuntimeService, value: JsonNode) { + val resourceProp = checkNotNull(resourceAssignment.property) { + "Failed in setting resource value for resource mapping $resourceAssignment" + } checkNotEmpty(resourceAssignment.name) { "Failed in setting resource value for resource mapping $resourceAssignment" } if (resourceAssignment.dictionaryName.isNullOrEmpty()) { resourceAssignment.dictionaryName = resourceAssignment.name - logger.warn("Missing dictionary key, setting with template key (${resourceAssignment.name}) as dictionary key (${resourceAssignment.dictionaryName})") + logger.warn("Missing dictionary key, setting with template key (${resourceAssignment.name}) " + + "as dictionary key (${resourceAssignment.dictionaryName})") } try { if (resourceProp.type.isNotEmpty()) { - val convertedValue = convertResourceValue(resourceProp.type, value) - logger.info("Setting Resource Value ($convertedValue) for Resource Name (${resourceAssignment.dictionaryName}) of type (${resourceProp.type})") - setResourceValue(resourceAssignment, raRuntimeService, convertedValue) + logger.info("Setting Resource Value ($value) for Resource Name " + + "(${resourceAssignment.dictionaryName}) of type (${resourceProp.type})") + setResourceValue(resourceAssignment, raRuntimeService, value) resourceAssignment.updatedDate = Date() resourceAssignment.updatedBy = BluePrintConstants.USER_SYSTEM resourceAssignment.status = BluePrintConstants.STATUS_SUCCESS } } catch (e: Exception) { - throw BluePrintProcessorException("Failed in setting value for template key (${resourceAssignment.name}) and " + - "dictionary key (${resourceAssignment.dictionaryName}) of type (${resourceProp.type}) with error message (${e.message})", e) + throw BluePrintProcessorException("Failed in setting value for template key " + + "(${resourceAssignment.name}) and dictionary key (${resourceAssignment.dictionaryName}) of " + + "type (${resourceProp.type}) with error message (${e.message})", e) } } - private fun setResourceValue(resourceAssignment: ResourceAssignment, raRuntimeService: ResourceAssignmentRuntimeService, value: JsonNode) { + private fun setResourceValue(resourceAssignment: ResourceAssignment, + raRuntimeService: ResourceAssignmentRuntimeService, value: JsonNode) { + // TODO("See if Validation is needed wrt to type before storing") raRuntimeService.putResolutionStore(resourceAssignment.name, value) raRuntimeService.putDictionaryStore(resourceAssignment.dictionaryName!!, value) resourceAssignment.property!!.value = value } - private fun convertResourceValue(type: String, value: Any?): JsonNode { - - return if (value == null || value is NullNode) { - logger.info("Returning {} value from convertResourceValue", value) - NullNode.instance - } else if (BluePrintTypes.validPrimitiveTypes().contains(type) && value is String) { - JacksonUtils.convertPrimitiveResourceValue(type, value) - } else if (value is String) { - JacksonUtils.jsonNode(value) - } else { - JacksonUtils.getJsonNode(value) - } - - } - fun setFailedResourceDataValue(resourceAssignment: ResourceAssignment, message: String?) { if (isNotEmpty(resourceAssignment.name)) { resourceAssignment.updatedDate = Date() @@ -106,8 +101,11 @@ class ResourceAssignmentUtils { @Throws(BluePrintProcessorException::class) fun assertTemplateKeyValueNotNull(resourceAssignment: ResourceAssignment) { - val resourceProp = checkNotNull(resourceAssignment.property) { "Failed to populate mandatory resource resource mapping $resourceAssignment" } - if (resourceProp.required != null && resourceProp.required!! && (resourceProp.value == null || resourceProp.value !is NullNode)) { + val resourceProp = checkNotNull(resourceAssignment.property) { + "Failed to populate mandatory resource resource mapping $resourceAssignment" + } + if (resourceProp.required != null && resourceProp.required!! + && (resourceProp.value == null || resourceProp.value !is NullNode)) { logger.error("failed to populate mandatory resource mapping ($resourceAssignment)") throw BluePrintProcessorException("failed to populate mandatory resource mapping ($resourceAssignment)") } @@ -138,8 +136,11 @@ class ResourceAssignmentUtils { return result } - fun transformToRARuntimeService(blueprintRuntimeService: BluePrintRuntimeService<*>, templateArtifactName: String): ResourceAssignmentRuntimeService { - val resourceAssignmentRuntimeService = ResourceAssignmentRuntimeService(blueprintRuntimeService.id(), blueprintRuntimeService.bluePrintContext()) + fun transformToRARuntimeService(blueprintRuntimeService: BluePrintRuntimeService<*>, + templateArtifactName: String): ResourceAssignmentRuntimeService { + + val resourceAssignmentRuntimeService = ResourceAssignmentRuntimeService(blueprintRuntimeService.id(), + blueprintRuntimeService.bluePrintContext()) resourceAssignmentRuntimeService.createUniqueId(templateArtifactName) resourceAssignmentRuntimeService.setExecutionContext(blueprintRuntimeService.getExecutionContext() as MutableMap<String, JsonNode>) @@ -147,10 +148,12 @@ class ResourceAssignmentUtils { } @Throws(BluePrintProcessorException::class) - fun getPropertyType(raRuntimeService: ResourceAssignmentRuntimeService, dataTypeName: String, propertyName: String): String { + fun getPropertyType(raRuntimeService: ResourceAssignmentRuntimeService, dataTypeName: String, + propertyName: String): String { lateinit var type: String try { val dataTypeProps = checkNotNull(raRuntimeService.bluePrintContext().dataTypeByName(dataTypeName)?.properties) + val propertyDefinition = checkNotNull(dataTypeProps[propertyName]) type = checkNotEmpty(propertyDefinition.type) { "Couldn't get data type ($dataTypeName)" } logger.trace("Data type({})'s property ({}) is ({})", dataTypeName, propertyName, type) diff --git a/ms/controllerblueprints/modules/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/CustomFunctions.kt b/ms/controllerblueprints/modules/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/CustomFunctions.kt index 0493deb5e..d45571cdf 100644 --- a/ms/controllerblueprints/modules/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/CustomFunctions.kt +++ b/ms/controllerblueprints/modules/blueprint-core/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/core/CustomFunctions.kt @@ -45,11 +45,32 @@ fun Double.asJsonPrimitive(): DoubleNode { return DoubleNode.valueOf(this) } -fun MutableMap<String, *>.asJsonNode(): JsonNode { +fun <T : Any?> T.asJsonType(): JsonNode { + return if (this == null) { + NullNode.instance + } else { + when (this) { + is JsonNode -> + this + is String -> + TextNode(this) + is Boolean -> + BooleanNode.valueOf(this) + is Int -> + IntNode.valueOf(this.toInt()) + is Double -> + DoubleNode.valueOf(this.toDouble()) + else -> + JacksonUtils.jsonNodeFromObject(this) + } + } +} + +fun Map<String, *>.asJsonNode(): JsonNode { return JacksonUtils.jsonNodeFromObject(this) } -fun MutableMap<String, *>.asObjectNode(): ObjectNode { +fun Map<String, *>.asObjectNode(): ObjectNode { return JacksonUtils.objectNodeFromObject(this) } @@ -60,7 +81,7 @@ fun format(message: String, vararg args: Any?): String { return message } -fun <T : Any> MutableMap<String, *>.castOptionalValue(key: String, valueType: KClass<T>): T? { +fun <T : Any> Map<String, *>.castOptionalValue(key: String, valueType: KClass<T>): T? { if (containsKey(key)) { return get(key) as? T } else { @@ -68,7 +89,7 @@ fun <T : Any> MutableMap<String, *>.castOptionalValue(key: String, valueType: KC } } -fun <T : Any> MutableMap<String, *>.castValue(key: String, valueType: KClass<T>): T { +fun <T : Any> Map<String, *>.castValue(key: String, valueType: KClass<T>): T { if (containsKey(key)) { return get(key) as T } else { @@ -93,35 +114,23 @@ fun JsonNode.rootFieldsToMap(): MutableMap<String, JsonNode> { fun MutableMap<String, JsonNode>.putJsonElement(key: String, value: Any) { - when (value) { - is JsonNode -> - this[key] = value - is String -> - this[key] = TextNode(value) - is Boolean -> - this[key] = BooleanNode.valueOf(value) - is Int -> - this[key] = IntNode.valueOf(value.toInt()) - is Double -> - this[key] = DoubleNode.valueOf(value.toDouble()) - else -> - this[key] = JacksonUtils.jsonNodeFromObject(value) - } + val convertedValue = value.asJsonType() + this[key] = convertedValue } -fun MutableMap<String, JsonNode>.getAsString(key: String): String { +fun Map<String, JsonNode>.getAsString(key: String): String { return this[key]?.asText() ?: throw BluePrintException("couldn't find value for key($key)") } -fun MutableMap<String, JsonNode>.getAsBoolean(key: String): Boolean { +fun Map<String, JsonNode>.getAsBoolean(key: String): Boolean { return this[key]?.asBoolean() ?: throw BluePrintException("couldn't find value for key($key)") } -fun MutableMap<String, JsonNode>.getAsInt(key: String): Int { +fun Map<String, JsonNode>.getAsInt(key: String): Int { return this[key]?.asInt() ?: throw BluePrintException("couldn't find value for key($key)") } -fun MutableMap<String, JsonNode>.getAsDouble(key: String): Double { +fun Map<String, JsonNode>.getAsDouble(key: String): Double { return this[key]?.asDouble() ?: throw BluePrintException("couldn't find value for key($key)") } |