Para hacer coincidir los URI jerárquicos con patrones y extraer argumentos, usa UriDeepLinkMatcher. Se basa en kotlinx.serialization para deserializar los argumentos que coinciden en tus clases de claves.
Para crear un UriDeepLinkMatcher, proporciona un patrón DeepLinkUri y el
serializador para la clave correspondiente:
@Serializable data class UserProfileKey(val id: String) : NavKey val userProfilePattern = DeepLinkUri("www.example.com/users/{id}") val userProfileMatcher = UriDeepLinkMatcher(userProfilePattern, serializer<UserProfileKey>()) val request = DeepLinkRequest(uri = "https://www.example.com/users/123") val matchResult = userProfileMatcher.match(request) val key = matchResult?.key // UserProfileKey(id = "123")
Para los URI no jerárquicos o los esquemas personalizados (como tel:), consulta Cómo crear comparadores de vínculos directos personalizados.
Patrones de coincidencia admitidos
UriDeepLinkMatcher hace coincidir los URIs según sus cinco componentes: esquema, autoridad, ruta de acceso, consulta y fragmento. En las siguientes secciones, se describen la sintaxis de patrones admitida, los marcadores de posición de argumentos y las reglas de coincidencia para cada componente.
Coincidencia de esquemas
Si no hay ningún esquema presente en el patrón de URI, se buscan coincidencias con http y https.
Para que coincida con un esquema específico, inclúyelo en el patrón. Como excepción, un esquema http en un patrón coincide con los URI de solicitud http y https, mientras que https en un patrón solo coincide con las solicitudes https.
| URI del patrón | URI de solicitud | Coincidencia |
|---|---|---|
www.example.com |
https://www.example.com |
✅ |
www.example.com |
http://www.example.com |
✅ |
http://www.example.com |
http://www.example.com |
✅ |
http://www.example.com |
https://www.example.com |
✅ |
https://www.example.com |
http://www.example.com |
❌ |
myapp://www.example.com |
myapp://www.example.com |
✅ |
Coincidencia de autoridad
UriDeepLinkMatcher realiza una coincidencia exacta que no distingue mayúsculas de minúsculas en la autoridad del URI (host y puerto opcional). No se admiten marcadores de posición ni comodines
en la autoridad, y no se extraen argumentos:
| URI del patrón | URI de solicitud | Coincidencia |
|---|---|---|
example.com |
https://example.com |
✅ |
example.com |
https://EXAMPLE.COM |
✅ |
example.com |
https://sub.example.com |
❌ |
example.com |
https://www.example.com |
❌ |
example.com |
https://example.com:8080 |
❌ |
example.com:8080 |
https://example.com:8080 |
✅ |
example.com:8080 |
https://example.com |
❌ |
Coincidencia de ruta
Se admiten los siguientes patrones de ruta de acceso:
| URI del patrón | URI de solicitud | Coincidencia | Argumentos extraídos |
|---|---|---|---|
www.example.com/users |
https://www.example.com/users |
✅ | Ninguno |
www.example.com/users/{id} |
https://www.example.com/users/123 |
✅ | id: "123" |
www.example.com/users/{first}-{last} |
https://www.example.com/users/john-doe |
✅ | first: "john", last: "doe" |
www.example.com/users/{id}/profile |
https://www.example.com/users//profile |
✅ | id: "" (cadena vacía) |
www.example.com/users/user_{id} |
https://www.example.com/users/user_123 |
✅ | id: "123" |
www.example.com/users/{userId}/posts/{postId} |
https://www.example.com/users/123/posts/456 |
✅ | userId: "123", postId: "456" |
www.example.com/users/.* |
https://www.example.com/users/john-doe |
✅ | Ninguno |
www.example.com/users |
https://www.example.com/users/ |
❌ (La barra diagonal final crea un segmento adicional) | N/A |
Concordancia de búsquedas
No es necesario que el orden de los parámetros de consulta en el URI de solicitud coincida con el orden en el URI de patrón. Además, se ignoran los parámetros presentes en el URI de solicitud, pero no en el URI de patrón.
Se admiten los siguientes patrones de parámetros de consulta:
| URI del patrón | URI de solicitud | Argumentos extraídos |
|---|---|---|
www.example.com/users?name={name} |
https://www.example.com/users?name=john |
name: "john" |
www.example.com/users?name={name} |
https://www.example.com/users?name= |
name: "" (cadena vacía) |
www.example.com/users?{rawQuery} |
https://www.example.com/users?anything&else |
rawQuery: ["anything", "else"] |
www.example.com/users?type=user_{id} |
https://www.example.com/users?type=user_123 |
id: "123" |
www.example.com/users?name={first}_{last} |
https://www.example.com/users?name=john_doe |
first: "john", last: "doe" |
www.example.com/users?list={list} |
https://www.example.com/users?list=10&list=20 |
list: ["10", "20"] |
www.example.com/users?name={name}&{other} |
https://www.example.com/users?name=john&tab=info |
name: "john", other: ["tab=info"] |
www.example.com/users?type=user_.* |
https://www.example.com/users?type=user_admin |
type: "admin" |
Coincidencia de fragmentos
Se admiten los siguientes tipos de patrones de fragmentos:
| URI del patrón | URI de solicitud | Argumentos extraídos |
|---|---|---|
www.example.com/#section1 |
https://www.example.com/#section1 |
Ninguno |
www.example.com/#section_{id} |
https://www.example.com/#section_123 |
id: "123" |
www.example.com/#section_.* |
https://www.example.com/#section_123 |
Ninguno |
Tipos de datos admitidos
UriDeepLinkMatcher admite la deserialización de argumentos de URI en tipos primitivos, enumeraciones, colecciones y objetos personalizados. La serialización se divide en dos categorías:
- Serialización estándar: Usa
kotlinx.serializationpara deserializar en lo siguiente:- Primitivas (
Boolean,Int,Long,Float,Double,Char,Byte,Short) yString - Enums
Set,ListoArrayde primitivas, cadenas o enumeraciones- Clases
@Serializableanidadas (cuyas propiedades se aplanan en marcadores de posición de URI individuales)
- Primitivas (
- Serialización personalizada con
DeepLinkSerializer: Convierte entre un soloStringy objetos personalizados, tipos externos (comojava.time.LocalDate) o colecciones delimitadas personalizadas.
Serialización estándar
UriDeepLinkMatcher funciona de inmediato para tipos estándar y estructuras
simplificadas sin necesidad de implementaciones de serializadores personalizados.
Primitivas y cadenas
UriDeepLinkMatcher decodifica automáticamente los tipos primitivos (Boolean, Int,
Long, Float, Double, Char, Byte, Short) y String:
@Serializable data class UserProfileKey(val id: Int) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/users/{id}"), serializer<UserProfileKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/users/123") val key = matcher.match(request)?.key // UserProfileKey(id = 123)
Enums
Los valores de enum se comparan con los nombres de los elementos de enum distinguiendo mayúsculas de minúsculas:
enum class SortOrder { RELEVANCE, DATE, POPULARITY } @Serializable data class ProductsKey(val sort: SortOrder) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/products?sort={sort}"), serializer<ProductsKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/products?sort=DATE") val key = matcher.match(request)?.key // ProductsKey(sort = SortOrder.DATE)
Colecciones de consultas repetidas
Los parámetros de consulta con claves repetidas (como ?id=10&id=20) se deserializan automáticamente en List<T>, Set<T> o Array<T>, donde T es un tipo primitivo, String o enum:
@Serializable data class FilteredItemsKey(val ids: List<Int>) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/items?id={ids}"), serializer<FilteredItemsKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/items?id=10&id=20") val key = matcher.match(request)?.key // FilteredItemsKey(ids = listOf(10, 20))
Clases @Serializable anidadas
Cuando un NavKey contiene una propiedad cuyo tipo es otra clase @Serializable,
UriDeepLinkMatcher compacta sus propiedades para que cada propiedad de la clase anidada
se asigne directamente a un parámetro de URI individual con el mismo nombre:
enum class SortOrder { RELEVANCE, DATE, POPULARITY } @Serializable data class SearchFilters( val category: String, val sortBy: SortOrder = SortOrder.RELEVANCE ) @Serializable data class SearchKey( val query: String, val page: Int = 1, // Flattened into {category} and {sortBy} val filters: SearchFilters ) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/search?q={query}&page={page}&category={category}&sortBy={sortBy}"), serializer<SearchKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/search?q=kotlin&category=books&sortBy=DATE") val key = matcher.match(request)?.key // SearchKey(query = "kotlin", page = 1, filters = SearchFilters(category = "books", sortBy = SortOrder.DATE))
Serialización personalizada con DeepLinkSerializer
Para deserializar objetos personalizados (como Filter(key = "brand", value =
"pixel")), tipos externos (como java.time.LocalDate) o cadenas delimitadas personalizadas (como valores separados por comas), extiende DeepLinkSerializer<T>.
DeepLinkSerializer<T> es un KSerializer<T> abstracto que convierte entre un String y un T:
abstract class DeepLinkSerializer<T : Any> : KSerializer<T> {
abstract val serialName: String
abstract fun deserialize(value: String): T
abstract fun serialize(value: T): String
}
Por ejemplo, considera las definiciones de Filter y FilterSerializer que se usan en los siguientes fragmentos:
@Serializable data class Filter(val key: String, val value: String) object FilterSerializer : DeepLinkSerializer<Filter>() { override val serialName: String = "com.example.Filter" override fun deserialize(value: String): Filter { val parts = value.split(":", limit = 2) if (parts.size < 2) { throw SerializationException("Invalid filter: $value. Expected key:value.") } return Filter(key = parts[0], value = parts[1]) } override fun serialize(value: Filter): String = "${value.key}:${value.value}" }
Objetos personalizados individuales
Para decodificar un objeto de una sola cadena de parámetro de URI (como ?filter=brand:google), anota la propiedad con @Serializable(with = ...):
@Serializable data class CatalogKey( @Serializable(with = FilterSerializer::class) val filter: Filter ) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/catalog?filter={filter}"), serializer<CatalogKey>() ) val request = DeepLinkRequest(uri = "https://www.example.com/catalog?filter=brand:google") val key = matcher.match(request)?.key // CatalogKey(filter = Filter("brand", "google"))
Objetos personalizados en parámetros de consulta repetidos
Para deserializar los parámetros de consulta repetidos en una colección de objetos personalizados
(List<T>, Set<T> o Array<T>), implementa DeepLinkSerializer<T> para el
tipo de elemento T y anota el argumento de tipo de la propiedad con
@Serializable(with = ...):
@Serializable data class SearchResultsKey( val query: String, val filters: List<@Serializable(with = FilterSerializer::class) Filter> = emptyList() ) : NavKey val searchResultsPattern = DeepLinkUri("www.example.com/search?q={query}&filter={filters}") val searchResultsMatcher = UriDeepLinkMatcher(searchResultsPattern, serializer<SearchResultsKey>()) val request = DeepLinkRequest(uri = "https://www.example.com/search?q=phone&filter=brand:google&filter=color:hazel") val matchResult = searchResultsMatcher.match(request) val key = matchResult?.key // SearchResultsKey(query = "phone", filters = listOf(Filter("brand", "google"), Filter("color", "hazel")))
Colecciones delimitadas en parámetros únicos
Para analizar valores separados por comas o delimitados de forma personalizada (como ?ids=1,2,3) en
una colección, implementa DeepLinkSerializer para el tipo de colección completo
y anota la propiedad con @Serializable(with = ...):
object IntListCsvSerializer : DeepLinkSerializer<List<Int>>() { override val serialName: String = "com.example.IntListCsv" override fun deserialize(value: String): List<Int> { if (value.isEmpty()) return emptyList() return value.split(",").map { it.trim().toInt() } } override fun serialize(value: List<Int>): String = value.joinToString(",") } @Serializable data class ItemListKey( @Serializable(with = IntListCsvSerializer::class) val ids: List<Int> ) : NavKey val itemListPattern = DeepLinkUri("www.example.com/items/{ids}") val itemListMatcher = UriDeepLinkMatcher(itemListPattern, serializer<ItemListKey>()) val request = DeepLinkRequest(uri = "https://www.example.com/items/10,20,30") val key = itemListMatcher.match(request)?.key // ItemListKey(ids = listOf(10, 20, 30))
Validación de argumentos y resultados de coincidencias
UriDeepLinkMatcher distingue entre discrepancias (devuelve null para que se puedan probar otros comparadores) y parámetros de configuración no admitidos (arroja una excepción).
Incongruencias
Se produce una discrepancia cuando un URI de solicitud entrante no cumple con los requisitos de patrón o tipo:
- Faltan parámetros obligatorios: Son propiedades de clave que no admiten valores nulos sin valores predeterminados cuyos parámetros de URI correspondientes no están presentes en el URI de solicitud.
- Fallas de análisis de tipos: Son valores de argumentos extraídos que no se pueden analizar
en el tipo de propiedad esperado (por ejemplo,
"abc"para una propiedadInt).
Cuando se produce una discrepancia, UriDeepLinkMatcher.match devuelve null, lo que permite
que se evalúen los comparadores posteriores.
Considera una clase de clave y un comparador configurados con valores predeterminados, objetos anidados y enumeraciones:
enum class MapLayer { STANDARD, SATELLITE, TERRAIN } @Serializable data class LayerOptions( val style: String, val layer: MapLayer = MapLayer.STANDARD ) @Serializable data class MapKey( val location: String, val zoom: Int = 12, val options: LayerOptions ) : NavKey val matcher = UriDeepLinkMatcher( DeepLinkUri("www.example.com/map/{location}?zoom={zoom}&style={style}&layer={layer}"), serializer<MapKey>() )
En la siguiente tabla, se muestran los resultados de coincidencia para varios URI de solicitud:
| URI de solicitud | Resultado de la decodificación | Resultado del partido |
|---|---|---|
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE |
Correcto (se proporcionaron todos los parámetros) | UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE))) |
https://www.example.com/map/paris?style=dark |
Correcto (zoom se establece de forma predeterminada en 12, layer en STANDARD) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map/paris?zoom=&style=dark |
Correcto (el parámetro de consulta opcional vacío usa el valor predeterminado 12) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map?style=dark |
Incongruencia (falta el parámetro obligatorio location) |
null |
https://www.example.com/map/paris?zoom=close&style=dark |
No coinciden ("close" no es un Int) |
null |
https://www.example.com/map/paris?style=dark&layer=HYBRID |
No coincide ("HYBRID" no está en la enumeración) |
null |
Parámetros de configuración no compatibles
Si tu clase de clave contiene tipos de datos no admitidos, UriDeepLinkMatcher arroja una excepción durante la coincidencia en lugar de devolver null.
- Mapas y colecciones multidimensionales:
UriDeepLinkMatchersolo admite colecciones unidimensionales de primitivas, cadenas, enumeraciones o tipos personalizados anotados con unDeepLinkSerializer. Los tiposMaparrojan unIllegalArgumentException, mientras que las colecciones anidadas (comoList<List<String>>) arrojan unSerializationException. - Colecciones de objetos personalizados sin anotaciones: Las colecciones de tipos personalizados (como
List<Filter>) arrojan unaSerializationException, a menos que el tipo de elemento esté anotado con unDeepLinkSerializer. - Clases anidadas sin compactar: Las clases
@Serializableanidadas no se pueden asignar a un solo marcador de posición (como?user={user}) sin unDeepLinkSerializer.
// Throws IllegalArgumentException: Map decoding is not supported. @Serializable data class InvalidKey(val tags: Map<String, String>) : NavKey // Throws SerializationException: Only collections of primitives are supported. @Serializable data class InvalidKey(val filters: List<Filter>) : NavKey
Comparación de UriMatchResult
Las instancias de UriMatchResult se clasifican con los siguientes criterios en orden:
- Tipo MatchResult:
UriMatchResulttiene una clasificación más alta que otros tipos deMatchResult. - Ruta de acceso exacta: Las coincidencias de ruta de acceso literal tienen una clasificación más alta que las coincidencias de marcador de posición o comodín.
- Recuento de argumentos de ruta de acceso: Las coincidencias con más argumentos de ruta de acceso tienen una clasificación más alta.
- Presencia de argumentos: Las coincidencias que capturan argumentos se clasifican más alto que las que no lo hacen.
- Recuento total de argumentos: La cantidad total de argumentos (ruta de acceso, consulta y fragmento) es el desempate final.
Personalizar UriDeepLinkMatcher
UriDeepLinkMatcher es una clase open que puedes usar como subclase para personalizar el comportamiento de la coincidencia de URI y la extracción de argumentos:
matchRequest: Es el punto de entrada coincidente de nivel superior para unDeepLinkRequestentrante. Anula esto para inspeccionar los extras de la solicitud o aplicar condiciones previas personalizadas antes de la coincidencia de URI.matchUri: Coincide con elDeepLinkUricon el patrón configurado. Anula esto para interceptar y normalizar los URIs entrantes (por ejemplo, reescribir subdominios dinámicos o formatos de ruta heredados) antes de llamar asuper.matchUri.matchArguments: Deserializa los mapas de argumentos de ruta de acceso, consulta y fragmento extraídos en una instancia de clave de navegación con elserializerproporcionado. Anula esto para insertar valores dinámicos o transformar argumentos antes de la creación de instancias de claves.
En el siguiente ejemplo, se muestra cómo crear una subclase de UriDeepLinkMatcher para normalizar los prefijos de ruta de URL heredados antes de la coincidencia:
class LegacyPrefixUriDeepLinkMatcher<T : Any>( uriPattern: DeepLinkUri, serializer: KSerializer<T> ) : UriDeepLinkMatcher<T>(uriPattern, serializer) { override fun matchUri(uri: DeepLinkUri): UriMatchResult<T>? { val path = uri.path val normalizedUri = if (path != null && path.startsWith("/legacy/")) { DeepLinkUri(uri.toString().replaceFirst("/legacy", "")) } else { uri } return super.matchUri(normalizedUri) } }