Per confrontare gli URI gerarchici con i pattern ed estrarre gli argomenti, utilizza UriDeepLinkMatcher. Si basa su kotlinx.serialization per deserializzare gli argomenti corrispondenti nelle classi chiave.
Per creare un UriDeepLinkMatcher, fornisci un pattern DeepLinkUri e il
serializzatore per la chiave corrispondente:
@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")
Per gli URI non gerarchici o gli schemi personalizzati (come tel:), consulta Crea matcher di deep link personalizzati.
Pattern di corrispondenza supportati
UriDeepLinkMatcher corrisponde agli URI in base ai loro cinque componenti: schema, autorità, percorso, query e frammento. Le sezioni seguenti descrivono la sintassi dei pattern supportati, i segnaposto degli argomenti e le regole di corrispondenza per ogni componente.
Corrispondenza dello schema
Se nel pattern URI non è presente alcuno schema, vengono trovate corrispondenze sia per http sia per https.
Per trovare una corrispondenza con uno schema specifico, includilo nel pattern. In via eccezionale, uno schema http in un pattern corrisponde sia agli URI della richiesta http sia a quelli https, mentre https in un pattern corrisponde solo alle richieste https.
| Pattern URI | URI della richiesta | Corrispondenza |
|---|---|---|
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 |
✅ |
Corrispondenza con l'autorità
UriDeepLinkMatcher esegue una corrispondenza esatta senza distinzione tra maiuscole e minuscole sull'autorità URI (host e porta facoltativa). I segnaposto o i caratteri jolly non sono supportati nell'autorità e non vengono estratti argomenti:
| Pattern URI | URI della richiesta | Corrispondenza |
|---|---|---|
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 |
❌ |
Corrispondenza percorso
Sono supportati i seguenti pattern di percorso:
| Pattern URI | URI della richiesta | Corrispondenza | Argomenti estratti |
|---|---|---|---|
www.example.com/users |
https://www.example.com/users |
✅ | Nessuno |
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: "" (stringa vuota) |
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 |
✅ | Nessuno |
www.example.com/users |
https://www.example.com/users/ |
❌ (lo slash finale crea un segmento aggiuntivo) | N/D |
Corrispondenza della query
L'ordine dei parametri di query nell'URI della richiesta non deve corrispondere all'ordine nell'URI del pattern. Inoltre, i parametri presenti nell'URI della richiesta, ma non nell'URI del pattern, vengono ignorati.
Sono supportati i seguenti pattern di parametri di query:
| Pattern URI | URI della richiesta | Argomenti estratti |
|---|---|---|
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: "" (stringa vuota) |
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" |
Corrispondenza dei frammenti
Sono supportati i seguenti tipi di pattern di frammenti:
| Pattern URI | URI della richiesta | Argomenti estratti |
|---|---|---|
www.example.com/#section1 |
https://www.example.com/#section1 |
Nessuno |
www.example.com/#section_{id} |
https://www.example.com/#section_123 |
id: "123" |
www.example.com/#section_.* |
https://www.example.com/#section_123 |
Nessuno |
Tipi di dati supportati
UriDeepLinkMatcher supporta la deserializzazione degli argomenti URI in tipi primitivi, enum, raccolte e oggetti personalizzati. La serializzazione si divide in due categorie:
- Serializzazione standard: utilizza
kotlinx.serializationper la deserializzazione in:- Primitive (
Boolean,Int,Long,Float,Double,Char,Byte,Short) eString - Enum
Set,ListoArraydi primitive, stringhe o enum- Classi
@Serializablenidificate (le cui proprietà sono appiattite in singoli segnaposto URI)
- Primitive (
- Serializzazione personalizzata con
DeepLinkSerializer: esegue la conversione tra un singoloStringe oggetti personalizzati, tipi esterni (comejava.time.LocalDate) o raccolte delimitate personalizzate.
Serializzazione standard
UriDeepLinkMatcher funziona immediatamente per i tipi standard e le strutture
piatte senza richiedere implementazioni di serializzatori personalizzati.
Primitive e stringhe
UriDeepLinkMatcher decodifica automaticamente i tipi primitivi (Boolean, Int,
Long, Float, Double, Char, Byte, Short) e 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)
Enum
I valori enum vengono confrontati con i nomi degli elementi enum facendo distinzione tra maiuscole e minuscole:
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)
Raccolte di query ripetute
I parametri di query con chiavi ripetute (ad esempio ?id=10&id=20) vengono deserializzati automaticamente in List<T>, Set<T> o Array<T>, dove T è 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))
Classi @Serializable nidificate
Quando un NavKey contiene una proprietà il cui tipo è un'altra classe @Serializable,
UriDeepLinkMatcher appiattisce le sue proprietà in modo che ogni proprietà della classe nidificata
venga mappata direttamente a un singolo parametro URI con lo stesso nome:
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))
Serializzazione personalizzata con DeepLinkSerializer
Per deserializzare oggetti personalizzati (come Filter(key = "brand", value =
"pixel")), tipi esterni (come java.time.LocalDate) o stringhe delimitate personalizzate (come valori separati da virgole), estendi DeepLinkSerializer<T>.
DeepLinkSerializer<T> è un KSerializer<T> astratto che esegue la conversione tra String e T:
abstract class DeepLinkSerializer<T : Any> : KSerializer<T> {
abstract val serialName: String
abstract fun deserialize(value: String): T
abstract fun serialize(value: T): String
}
Ad esempio, considera le definizioni Filter e FilterSerializer utilizzate nei seguenti snippet:
@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}" }
Singoli oggetti personalizzati
Per decodificare un oggetto da una singola stringa di parametri URI (ad esempio ?filter=brand:google), annota la proprietà 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"))
Oggetti personalizzati nei parametri di query ripetuti
Per deserializzare i parametri di query ripetuti in una raccolta di oggetti personalizzati (List<T>, Set<T> o Array<T>), implementa DeepLinkSerializer<T> per il tipo di elemento T e annota l'argomento del tipo della proprietà 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")))
Raccolte delimitate in singoli parametri
Per analizzare i valori separati da virgole o delimitati in modo personalizzato (ad esempio ?ids=1,2,3) in una raccolta, implementa DeepLinkSerializer per l'intero tipo di raccolta e annota la proprietà 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))
Risultati della convalida e della corrispondenza degli argomenti
UriDeepLinkMatcher distingue tra mancate corrispondenze (restituisce null in modo che sia possibile provare altri matcher) e configurazioni non supportate (genera un'eccezione).
Mancate corrispondenze
Si verifica una mancata corrispondenza quando l'URI di una richiesta in entrata non soddisfa i requisiti del pattern o del tipo:
- Mancano parametri obbligatori: proprietà chiave non nullable senza valori predefiniti i cui parametri URI corrispondenti sono assenti dall'URI della richiesta.
- Errori di analisi del tipo: valori degli argomenti estratti che non possono essere analizzati nel tipo di proprietà previsto (ad esempio,
"abc"per una proprietàInt).
Quando si verifica una mancata corrispondenza, UriDeepLinkMatcher.match restituisce null, consentendo la valutazione dei matcher successivi.
Considera una classe di chiavi e un matcher configurati con valori predefiniti, oggetti nidificati ed enum:
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>() )
La tabella seguente mostra i risultati della corrispondenza per vari URI della richiesta:
| URI della richiesta | Risultato della decodifica | Risultato della corrispondenza |
|---|---|---|
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE |
Success (tutti i parametri forniti) | UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE))) |
https://www.example.com/map/paris?style=dark |
Success (zoom il valore predefinito è 12, layer è STANDARD) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map/paris?zoom=&style=dark |
Successo (il parametro di query facoltativo vuoto utilizza il valore predefinito 12) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map?style=dark |
Mancata corrispondenza (parametro location obbligatorio mancante) |
null |
https://www.example.com/map/paris?zoom=close&style=dark |
Mancata corrispondenza ("close" non è un Int) |
null |
https://www.example.com/map/paris?style=dark&layer=HYBRID |
Mancata corrispondenza ("HYBRID" non è nell'enum) |
null |
Configurazioni non supportate
Se la classe della chiave contiene tipi di dati non supportati, UriDeepLinkMatcher genera un'eccezione durante la corrispondenza anziché restituire null.
- Mappe e raccolte multidimensionali:
UriDeepLinkMatchersupporta solo raccolte monodimensionali di primitive, stringhe, enum o tipi personalizzati annotati con unDeepLinkSerializer.Mapgenerano unIllegalArgumentException, mentre le raccolte nidificate (comeList<List<String>>) generano unSerializationException. - Raccolte di oggetti personalizzati non annotati: le raccolte di tipi personalizzati (come
List<Filter>) generano unSerializationException, a meno che il tipo di elemento non sia annotato con unDeepLinkSerializer. - Classi nidificate non appiattite: le classi
@Serializablenidificate non possono essere mappate a un singolo segnaposto (come?user={user}) senza 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
Confronto UriMatchResult
Le istanze UriMatchResult vengono classificate utilizzando i seguenti criteri in ordine:
- Tipo MatchResult:
UriMatchResultha un ranking più alto rispetto ad altri tipi diMatchResult. - Percorso esatto: le corrispondenze dei percorsi letterali hanno un ranking più alto rispetto alle corrispondenze dei segnaposto o dei caratteri jolly.
- Numero di argomenti del percorso: le corrispondenze con più argomenti del percorso hanno un ranking più elevato.
- Presenza di argomenti: le corrispondenze che acquisiscono argomenti hanno un ranking più elevato rispetto a quelle che non lo fanno.
- Numero totale di argomenti: il numero totale di argomenti (percorso, query, frammento) è l'ultimo criterio di spareggio.
Personalizza UriDeepLinkMatcher
UriDeepLinkMatcher è una classe open che puoi sottoclassare per personalizzare il comportamento di corrispondenza degli URI e di estrazione degli argomenti:
matchRequest: entry point di corrispondenza di primo livello per unDeepLinkRequestin entrata. Esegui l'override di questo metodo per ispezionare gli extra della richiesta o applicare precondizioni personalizzate prima della corrispondenza dell'URI.matchUri: confrontaDeepLinkUricon il pattern configurato. Esegui l'override di questo metodo per intercettare e normalizzare gli URI in entrata (ad esempio, riscrivendo i sottodomini dinamici o i formati dei percorsi legacy) prima di chiamaresuper.matchUri.matchArguments: deserializza le mappe degli argomenti di percorso, query e frammento estratti in un'istanza della chiave di navigazione utilizzando l'oggettoserializerfornito. Esegui l'override di questo metodo per inserire valori dinamici o trasformare gli argomenti prima della creazione dell'istanza della chiave.
L'esempio seguente mostra la sottoclasse di UriDeepLinkMatcher per normalizzare i prefissi dei percorsi degli URL legacy prima della corrispondenza:
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) } }