diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/model/Query.kt b/search-service/src/main/kotlin/com/egm/stellio/search/model/Query.kt index 0d6c1abb0a..f9f524e762 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/model/Query.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/model/Query.kt @@ -22,7 +22,8 @@ data class Query private constructor( val q: String? = null, val geoQ: UnparsedGeoQuery? = null, val temporalQ: UnparsedTemporalQuery? = null, - val scopeQ: String? = null + val scopeQ: String? = null, + val lang: String? = null, ) { companion object { operator fun invoke(queryBody: String): Either = either { diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/util/EntitiesQueryUtils.kt b/search-service/src/main/kotlin/com/egm/stellio/search/util/EntitiesQueryUtils.kt index f4d86e7a77..99891b616b 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/util/EntitiesQueryUtils.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/util/EntitiesQueryUtils.kt @@ -63,16 +63,6 @@ fun EntitiesQuery.validateMinimalQueryEntitiesParameters(): Either, - contexts: List -): Either = either { - val query = Query(requestBody).bind() - composeEntitiesQueryFromPostRequest(defaultPagination, query, requestParams, contexts).bind() -} - fun composeEntitiesQueryFromPostRequest( defaultPagination: ApplicationProperties.Pagination, query: Query, @@ -152,11 +142,10 @@ fun composeTemporalEntitiesQuery( fun composeTemporalEntitiesQueryFromPostRequest( defaultPagination: ApplicationProperties.Pagination, - requestBody: String, + query: Query, requestParams: MultiValueMap, contexts: List ): Either = either { - val query = Query(requestBody).bind() val entitiesQuery = composeEntitiesQueryFromPostRequest( defaultPagination, query, diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityOperationHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityOperationHandler.kt index 4a145e9748..9047148c65 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityOperationHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/web/EntityOperationHandler.kt @@ -3,6 +3,7 @@ package com.egm.stellio.search.web import arrow.core.* import arrow.core.raise.either import com.egm.stellio.search.authorization.AuthorizationService +import com.egm.stellio.search.model.Query import com.egm.stellio.search.service.EntityEventService import com.egm.stellio.search.service.EntityOperationService import com.egm.stellio.search.service.EntityPayloadService @@ -273,10 +274,11 @@ class EntityOperationHandler( val sub = getSubFromSecurityContext() val contexts = getContextFromLinkHeaderOrDefault(httpHeaders).bind() val mediaType = getApplicableMediaType(httpHeaders).bind() + val query = Query(requestBody.awaitFirst()).bind() val entitiesQuery = composeEntitiesQueryFromPostRequest( applicationProperties.pagination, - requestBody.awaitFirst(), + query, params, contexts ).bind() @@ -290,6 +292,8 @@ class EntityOperationHandler( val compactedEntities = compactEntities(filteredEntities, contexts) val ngsiLdDataRepresentation = parseRepresentations(params, mediaType) + .copy(languageFilter = query.lang) + buildQueryResponse( compactedEntities.toFinalRepresentation(ngsiLdDataRepresentation), count, diff --git a/search-service/src/main/kotlin/com/egm/stellio/search/web/TemporalEntityOperationsHandler.kt b/search-service/src/main/kotlin/com/egm/stellio/search/web/TemporalEntityOperationsHandler.kt index a3675e2e12..02bf29ba6a 100644 --- a/search-service/src/main/kotlin/com/egm/stellio/search/web/TemporalEntityOperationsHandler.kt +++ b/search-service/src/main/kotlin/com/egm/stellio/search/web/TemporalEntityOperationsHandler.kt @@ -2,6 +2,7 @@ package com.egm.stellio.search.web import arrow.core.raise.either import com.egm.stellio.search.authorization.AuthorizationService +import com.egm.stellio.search.model.Query import com.egm.stellio.search.service.QueryService import com.egm.stellio.search.util.composeTemporalEntitiesQueryFromPostRequest import com.egm.stellio.shared.config.ApplicationProperties @@ -36,11 +37,12 @@ class TemporalEntityOperationsHandler( val sub = getSubFromSecurityContext() val contexts = getContextFromLinkHeaderOrDefault(httpHeaders).bind() val mediaType = getApplicableMediaType(httpHeaders).bind() + val query = Query(requestBody.awaitFirst()).bind() val temporalEntitiesQuery = composeTemporalEntitiesQueryFromPostRequest( applicationProperties.pagination, - requestBody.awaitFirst(), + query, params, contexts ).bind() @@ -55,6 +57,7 @@ class TemporalEntityOperationsHandler( val compactedEntities = compactEntities(temporalEntities, contexts) val ngsiLdDataRepresentation = parseRepresentations(params, mediaType) + .copy(languageFilter = query.lang) buildQueryResponse( compactedEntities.toFinalRepresentation(ngsiLdDataRepresentation), diff --git a/search-service/src/test/kotlin/com/egm/stellio/search/util/EntitiesQueryUtilsTests.kt b/search-service/src/test/kotlin/com/egm/stellio/search/util/EntitiesQueryUtilsTests.kt index 49698071e8..5b3836cac5 100644 --- a/search-service/src/test/kotlin/com/egm/stellio/search/util/EntitiesQueryUtilsTests.kt +++ b/search-service/src/test/kotlin/com/egm/stellio/search/util/EntitiesQueryUtilsTests.kt @@ -1,8 +1,13 @@ package com.egm.stellio.search.util +import arrow.core.Either +import arrow.core.raise.either import com.egm.stellio.search.model.AttributeInstance +import com.egm.stellio.search.model.EntitiesQuery +import com.egm.stellio.search.model.Query import com.egm.stellio.search.model.TemporalQuery import com.egm.stellio.shared.config.ApplicationProperties +import com.egm.stellio.shared.model.APIException import com.egm.stellio.shared.model.BadRequestDataException import com.egm.stellio.shared.model.GeoQuery import com.egm.stellio.shared.util.* @@ -15,6 +20,7 @@ import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import org.springframework.test.context.ActiveProfiles import org.springframework.util.LinkedMultiValueMap +import org.springframework.util.MultiValueMap import java.net.URI import java.time.ZonedDateTime @@ -222,6 +228,16 @@ class EntitiesQueryUtilsTests { } } + private fun composeEntitiesQueryFromPostRequest( + defaultPagination: ApplicationProperties.Pagination, + requestBody: String, + requestParams: MultiValueMap, + contexts: List + ): Either = either { + val query = Query(requestBody).bind() + composeEntitiesQueryFromPostRequest(defaultPagination, query, requestParams, contexts).bind() + } + @Test fun `it should not validate the temporal query if type or attrs are not present`() = runTest { val queryParams = LinkedMultiValueMap() diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt index c6ceebb1c5..aa71d6df8d 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/model/CompactedEntity.kt @@ -11,51 +11,82 @@ import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_TYPE_TERM import com.egm.stellio.shared.util.JsonLdUtils.JSONLD_VALUE_TERM import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATASET_ID_TERM import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_DATASET_TERM +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_GEOPROPERTY_TERM +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_JSONPROPERTY_TERM +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_LANGUAGEPROPERTY_TERM +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_LANG_TERM import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_NONE_TERM +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_PROPERTY_TERM +import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_RELATIONSHIP_TERM import com.egm.stellio.shared.util.JsonLdUtils.NGSILD_SYSATTRS_TERMS +import java.util.Locale typealias CompactedEntity = Map -fun CompactedEntity.toKeyValues(): Map = - this.mapValues { (_, value) -> simplifyRepresentation(value) } - -private fun simplifyRepresentation(value: Any): Any = - when (value) { - // an attribute with a single instance - is Map<*, *> -> simplifyValue(value as Map) - // an attribute with multiple instances - is List<*> -> { - when (value.first()) { - is Map<*, *> -> simplifyMultiInstanceAttribute(value as List>) - // we keep @context value as it is (List) - else -> value - } - } - // keep id, type and other non-reified properties as they are (typically string or list) - else -> value +fun CompactedEntity.toSimplifiedAttributes(): Map = + this.mapValues { (_, value) -> + applyAttributeTransformation(value, null, ::simplifyAttribute, ::simplifyMultiInstanceAttribute) } -private fun simplifyMultiInstanceAttribute(value: List>): Map> { +private fun simplifyMultiInstanceAttribute( + value: List>, + transformationParameters: Map? +): Map> { val datasetIds = value.map { val datasetId = (it[NGSILD_DATASET_ID_TERM] as? String) ?: NGSILD_NONE_TERM - val datasetValue: Any = simplifyValue(it) + val datasetValue: Any = simplifyAttribute(it, transformationParameters) Pair(datasetId, datasetValue) } return mapOf(NGSILD_DATASET_TERM to datasetIds.toMap()) } -private fun simplifyValue(value: Map): Any { +@SuppressWarnings("UnusedParameter") +private fun simplifyAttribute(value: Map, transformationParameters: Map?): Any { val attributeCompactedType = AttributeCompactedType.forKey(value[JSONLD_TYPE_TERM] as String)!! return when (attributeCompactedType) { - PROPERTY, GEOPROPERTY -> { - value.getOrDefault(JSONLD_VALUE_TERM, value) - } + PROPERTY, GEOPROPERTY -> value.getOrDefault(JSONLD_VALUE_TERM, value) RELATIONSHIP -> value.getOrDefault(JSONLD_OBJECT, value) JSONPROPERTY -> mapOf(JSONLD_JSON_TERM to value.getOrDefault(JSONLD_JSON_TERM, value)) LANGUAGEPROPERTY -> mapOf(JSONLD_LANGUAGEMAP_TERM to value.getOrDefault(JSONLD_LANGUAGEMAP_TERM, value)) } } +fun CompactedEntity.toFilteredLanguageProperties(languageFilter: String): CompactedEntity = + this.mapValues { (_, value) -> + applyAttributeTransformation( + value, + mapOf(QUERY_PARAM_LANG to languageFilter), + ::filterLanguageProperty, + ::filterMultiInstanceLanguageProperty + ) + } + +private fun filterMultiInstanceLanguageProperty( + value: List>, + transformationParameters: Map? +): Any = + value.map { + filterLanguageProperty(it, transformationParameters) + } + +private fun filterLanguageProperty(value: Map, transformationParameters: Map?): Any { + val attributeCompactedType = AttributeCompactedType.forKey(value[JSONLD_TYPE_TERM] as String)!! + return when (attributeCompactedType) { + LANGUAGEPROPERTY -> { + val localeRanges = Locale.LanguageRange.parse(transformationParameters?.get(QUERY_PARAM_LANG)!!) + val propertyLocales = (value[JSONLD_LANGUAGEMAP_TERM] as Map).keys.sorted() + val bestLocaleMatch = Locale.filterTags(localeRanges, propertyLocales) + .getOrElse(0) { _ -> propertyLocales.first() } + mapOf( + JSONLD_TYPE_TERM to NGSILD_PROPERTY_TERM, + JSONLD_VALUE_TERM to (value[JSONLD_LANGUAGEMAP_TERM] as Map)[bestLocaleMatch], + NGSILD_LANG_TERM to bestLocaleMatch + ) + } + else -> value + } +} + fun CompactedEntity.toGeoJson(geometryProperty: String): Map { val geometryAttributeContent = this[geometryProperty] as? Map val geometryPropertyValue = geometryAttributeContent?.let { @@ -103,7 +134,12 @@ fun CompactedEntity.toFinalRepresentation( if (!ngsiLdDataRepresentation.includeSysAttrs) it.withoutSysAttrs(ngsiLdDataRepresentation.timeproperty) else it }.let { - if (ngsiLdDataRepresentation.attributeRepresentation == AttributeRepresentation.SIMPLIFIED) it.toKeyValues() + if (ngsiLdDataRepresentation.languageFilter != null) + it.toFilteredLanguageProperties(ngsiLdDataRepresentation.languageFilter) + else it + }.let { + if (ngsiLdDataRepresentation.attributeRepresentation == AttributeRepresentation.SIMPLIFIED) + it.toSimplifiedAttributes() else it }.let { when (ngsiLdDataRepresentation.entityRepresentation) { @@ -136,14 +172,35 @@ fun List.toFinalRepresentation( } enum class AttributeCompactedType(val key: String) { - PROPERTY("Property"), - RELATIONSHIP("Relationship"), - GEOPROPERTY("GeoProperty"), - JSONPROPERTY("JsonProperty"), - LANGUAGEPROPERTY("LanguageProperty"); + PROPERTY(NGSILD_PROPERTY_TERM), + RELATIONSHIP(NGSILD_RELATIONSHIP_TERM), + GEOPROPERTY(NGSILD_GEOPROPERTY_TERM), + JSONPROPERTY(NGSILD_JSONPROPERTY_TERM), + LANGUAGEPROPERTY(NGSILD_LANGUAGEPROPERTY_TERM); companion object { fun forKey(key: String): AttributeCompactedType? = entries.find { it.key == key } } } + +private fun applyAttributeTransformation( + value: Any, + transformationParameters: Map?, + onSingleInstance: (Map, Map?) -> Any, + onMultiInstance: (List>, Map?) -> Any +): Any = + when (value) { + // an attribute with a single instance + is Map<*, *> -> onSingleInstance(value as Map, transformationParameters) + // an attribute with multiple instances + is List<*> -> { + when (value.first()) { + is Map<*, *> -> onMultiInstance(value as List>, transformationParameters) + // we keep @context value as it is (List) + else -> value + } + } + // keep id, type and other non-reified properties as they are (typically string or list) + else -> value + } diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/model/NgsiLdDataRepresentation.kt b/shared/src/main/kotlin/com/egm/stellio/shared/model/NgsiLdDataRepresentation.kt index 4e69b84fc8..f1983d7b4e 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/model/NgsiLdDataRepresentation.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/model/NgsiLdDataRepresentation.kt @@ -11,6 +11,7 @@ data class NgsiLdDataRepresentation( val entityRepresentation: EntityRepresentation, val attributeRepresentation: AttributeRepresentation, val includeSysAttrs: Boolean, + val languageFilter: String? = null, // In the case of GeoJSON Entity representation, // this parameter indicates which GeoProperty to use for the toplevel geometry field val geometryProperty: String? = null, diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/util/ApiUtils.kt b/shared/src/main/kotlin/com/egm/stellio/shared/util/ApiUtils.kt index e994080c91..2bfc7a3ebb 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/util/ApiUtils.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/util/ApiUtils.kt @@ -37,6 +37,7 @@ const val QUERY_PARAM_ATTRS: String = "attrs" const val QUERY_PARAM_Q: String = "q" const val QUERY_PARAM_SCOPEQ: String = "scopeQ" const val QUERY_PARAM_GEOMETRY_PROPERTY: String = "geometryProperty" +const val QUERY_PARAM_LANG: String = "lang" const val QUERY_PARAM_OPTIONS: String = "options" const val QUERY_PARAM_OPTIONS_SYSATTRS_VALUE: String = "sysAttrs" const val QUERY_PARAM_OPTIONS_KEYVALUES_VALUE: String = "keyValues" @@ -218,6 +219,7 @@ fun parseRepresentations( val includeSysAttrs = optionsParam.contains(QUERY_PARAM_OPTIONS_SYSATTRS_VALUE) val attributeRepresentation = optionsParam.contains(QUERY_PARAM_OPTIONS_KEYVALUES_VALUE) .let { if (it) AttributeRepresentation.SIMPLIFIED else AttributeRepresentation.NORMALIZED } + val languageFilter = requestParams.getFirst(QUERY_PARAM_LANG) val entityRepresentation = EntityRepresentation.forMediaType(acceptMediaType) val geometryProperty = if (entityRepresentation == EntityRepresentation.GEO_JSON) @@ -229,6 +231,7 @@ fun parseRepresentations( entityRepresentation, attributeRepresentation, includeSysAttrs, + languageFilter, geometryProperty, timeproperty ) diff --git a/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonLdUtils.kt b/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonLdUtils.kt index 433463683c..c03f8b47fb 100644 --- a/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonLdUtils.kt +++ b/shared/src/main/kotlin/com/egm/stellio/shared/util/JsonLdUtils.kt @@ -70,6 +70,7 @@ object JsonLdUtils { const val JSONLD_CONTEXT = "@context" const val NGSILD_SCOPE_TERM = "scope" const val NGSILD_SCOPE_PROPERTY = "https://uri.etsi.org/ngsi-ld/$NGSILD_SCOPE_TERM" + const val NGSILD_LANG_TERM = "lang" const val NGSILD_NONE_TERM = "@none" const val NGSILD_DATASET_TERM = "dataset" val JSONLD_EXPANDED_ENTITY_SPECIFIC_MEMBERS = setOf(JSONLD_TYPE, NGSILD_SCOPE_PROPERTY) diff --git a/shared/src/test/kotlin/com/egm/stellio/shared/model/CompactedEntityTests.kt b/shared/src/test/kotlin/com/egm/stellio/shared/model/CompactedEntityTests.kt index e5d56f9bb5..c0fe636631 100644 --- a/shared/src/test/kotlin/com/egm/stellio/shared/model/CompactedEntityTests.kt +++ b/shared/src/test/kotlin/com/egm/stellio/shared/model/CompactedEntityTests.kt @@ -127,7 +127,7 @@ class CompactedEntityTests { val normalizedMap = normalizedEntity.deserializeAsMap() val simplifiedMap = simplifiedEntity.deserializeAsMap() - val resultMap = normalizedMap.toKeyValues() + val resultMap = normalizedMap.toSimplifiedAttributes() assertEquals(simplifiedMap, resultMap) } @@ -137,7 +137,7 @@ class CompactedEntityTests { val normalizedMap = normalizedMultiAttributeEntity.deserializeAsMap() val simplifiedMap = simplifiedMultiAttributeEntity.deserializeAsMap() - val resultMap = normalizedMap.toKeyValues() + val resultMap = normalizedMap.toSimplifiedAttributes() assertEquals(simplifiedMap, resultMap) } @@ -160,7 +160,7 @@ class CompactedEntityTests { """.trimIndent() .deserializeAsMap() - val simplifiedRepresentation = compactedEntity.toKeyValues() + val simplifiedRepresentation = compactedEntity.toSimplifiedAttributes() val expectedSimplifiedRepresentation = """ { @@ -195,7 +195,7 @@ class CompactedEntityTests { """.trimIndent() .deserializeAsMap() - val simplifiedRepresentation = compactedEntity.toKeyValues() + val simplifiedRepresentation = compactedEntity.toSimplifiedAttributes() val expectedSimplifiedRepresentation = """ { @@ -744,7 +744,7 @@ class CompactedEntityTests { EntityRepresentation.GEO_JSON, AttributeRepresentation.SIMPLIFIED, includeSysAttrs = false, - JsonLdUtils.NGSILD_LOCATION_TERM + geometryProperty = JsonLdUtils.NGSILD_LOCATION_TERM ) ) @@ -768,7 +768,7 @@ class CompactedEntityTests { """.trimIndent() .deserializeAsMap() - val simplifiedRepresentation = compactedEntity.toKeyValues() + val simplifiedRepresentation = compactedEntity.toSimplifiedAttributes() val expectedSimplifiedRepresentation = """ { diff --git a/shared/src/test/kotlin/com/egm/stellio/shared/model/LanguageFilterTests.kt b/shared/src/test/kotlin/com/egm/stellio/shared/model/LanguageFilterTests.kt new file mode 100644 index 0000000000..6c328ba1de --- /dev/null +++ b/shared/src/test/kotlin/com/egm/stellio/shared/model/LanguageFilterTests.kt @@ -0,0 +1,166 @@ +package com.egm.stellio.shared.model + +import com.egm.stellio.shared.util.JsonUtils.deserializeAsMap +import com.egm.stellio.shared.util.JsonUtils.serializeObject +import com.egm.stellio.shared.util.assertJsonPayloadsAreEqual +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream + +class LanguageFilterTests { + + companion object { + + @JvmStatic + fun normalizedResultsProvider(): Stream { + return Stream.of( + Arguments.of( + "nl", + """ + "languageProperty": { + "type": "Property", + "value": "Grote Markt", + "lang": "nl" + } + """.trimIndent() + ), + Arguments.of( + "en", + """ + "languageProperty": { + "type": "Property", + "value": "Grand Place", + "lang": "fr" + } + """.trimIndent() + ), + Arguments.of( + "*", + """ + "languageProperty": { + "type": "Property", + "value": "Grand Place", + "lang": "fr" + } + """.trimIndent() + ), + Arguments.of( + "fr-CH,fr;q=0.9,en;q=0.8,*;q=0.5", + """ + "languageProperty": { + "type": "Property", + "value": "Grand Place", + "lang": "fr" + } + """.trimIndent() + ) + ) + } + + @JvmStatic + fun simplifiedResultsProvider(): Stream { + return Stream.of( + Arguments.of( + "nl", + """ + "languageProperty": "Grote Markt" + """.trimIndent() + ), + Arguments.of( + "en", + """ + "languageProperty": "Grand Place" + """.trimIndent() + ), + Arguments.of( + "*", + """ + "languageProperty": "Grand Place" + """.trimIndent() + ), + Arguments.of( + "fr-CH,fr;q=0.9,en;q=0.8,*;q=0.5", + """ + "languageProperty": "Grand Place" + """.trimIndent() + ) + ) + } + } + + @ParameterizedTest + @MethodSource("com.egm.stellio.shared.model.LanguageFilterTests#normalizedResultsProvider") + fun `it should return the normalized representation of a LanguageProperty with a language filter`( + languageFilter: String, + expectedAttribute: String + ) { + val compactedEntity = """ + { + "id": "urn:ngsi-ld:Entity:01", + "type": "Entity", + "languageProperty": { + "type": "LanguageProperty", + "languageMap": { + "fr": "Grand Place", + "nl": "Grote Markt" + } + } + } + """.trimIndent() + .deserializeAsMap() + + val filteredRepresentation = compactedEntity.toFilteredLanguageProperties(languageFilter) + + val expectedFilteredRepresentation = """ + { + "id": "urn:ngsi-ld:Entity:01", + "type": "Entity", + $expectedAttribute + } + """.trimIndent() + + assertJsonPayloadsAreEqual(expectedFilteredRepresentation, serializeObject(filteredRepresentation)) + } + + @ParameterizedTest + @MethodSource("com.egm.stellio.shared.model.LanguageFilterTests#simplifiedResultsProvider") + fun `it should return the simplfied representation of a LanguageProperty with a language filter`( + languageFilter: String, + expectedAttribute: String + ) { + val compactedEntity = """ + { + "id": "urn:ngsi-ld:Entity:01", + "type": "Entity", + "languageProperty": { + "type": "LanguageProperty", + "languageMap": { + "fr": "Grand Place", + "nl": "Grote Markt" + } + } + } + """.trimIndent() + .deserializeAsMap() + + val filteredRepresentation = compactedEntity.toFinalRepresentation( + NgsiLdDataRepresentation( + EntityRepresentation.JSON, + AttributeRepresentation.SIMPLIFIED, + false, + languageFilter + ) + ) + + val expectedFilteredRepresentation = """ + { + "id": "urn:ngsi-ld:Entity:01", + "type": "Entity", + $expectedAttribute + } + """.trimIndent() + + assertJsonPayloadsAreEqual(expectedFilteredRepresentation, serializeObject(filteredRepresentation)) + } +}