Verwenden Sie UriDeepLinkMatcher, um hierarchische URIs mit Mustern abzugleichen und Argumente zu extrahieren. Es verwendet kotlinx.serialization, um übereinstimmende Argumente in Ihre Schlüsselklassen zu deserialisieren.
Wenn Sie einen UriDeepLinkMatcher erstellen möchten, geben Sie ein Muster DeepLinkUri und den Serializer für den entsprechenden Schlüssel an:
@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")
Informationen zu nicht hierarchischen URIs oder benutzerdefinierten Schemas (z. B. tel:) finden Sie unter Benutzerdefinierte Deeplink-Matcher erstellen.
Unterstützte Abgleichsmuster
UriDeepLinkMatcher gleicht URIs anhand ihrer fünf Komponenten ab: Schema, Authority, Pfad, Abfrage und Fragment. In den folgenden Abschnitten werden die unterstützte Mustersyntax, die Argumentplatzhalter und die Abgleichsregeln für die einzelnen Komponenten beschrieben.
Schemaabgleich
Wenn im URI-Muster kein Schema vorhanden ist, werden sowohl http als auch https abgeglichen.
Wenn Sie ein bestimmtes Schema abgleichen möchten, fügen Sie es in das Muster ein. Als Ausnahme gilt, dass ein http-Schema in einem Muster sowohl mit http- als auch mit https-Anfrage-URIs übereinstimmt, während https in einem Muster nur mit https-Anfragen übereinstimmt.
| Muster-URI | Anfrage-URI | Übereinstimmung |
|---|---|---|
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 |
✅ |
Abgleich der Zertifizierungsstelle
UriDeepLinkMatcher führt einen genauen Abgleich der URI-Autorität (Host und optionaler Port) ohne Berücksichtigung der Groß-/Kleinschreibung durch. Platzhalter werden in der Authority nicht unterstützt und es werden keine Argumente extrahiert:
| Muster-URI | Anfrage-URI | Übereinstimmung |
|---|---|---|
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 |
❌ |
Pfadabgleich
Die folgenden Pfadmuster werden unterstützt:
| Muster-URI | Anfrage-URI | Übereinstimmung | Extrahierte Argumente |
|---|---|---|---|
www.example.com/users |
https://www.example.com/users |
✅ | Keine |
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: "" (leerer String) |
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 |
✅ | Keine |
www.example.com/users |
https://www.example.com/users/ |
❌ (Der abschließende Schrägstrich erstellt ein zusätzliches Segment) | – |
Query-Matching
Die Reihenfolge der Suchparameter im Anfrage-URI muss nicht mit der Reihenfolge im Muster-URI übereinstimmen. Außerdem werden Parameter ignoriert, die im Anfrage-URI, aber nicht im Muster-URI vorhanden sind.
Die folgenden Suchparametermuster werden unterstützt:
| Muster-URI | Anfrage-URI | Extrahierte Argumente |
|---|---|---|
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: "" (leerer String) |
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" |
Fragmentabgleich
Die folgenden Fragmentmustertypen werden unterstützt:
| Muster-URI | Anfrage-URI | Extrahierte Argumente |
|---|---|---|
www.example.com/#section1 |
https://www.example.com/#section1 |
Keine |
www.example.com/#section_{id} |
https://www.example.com/#section_123 |
id: "123" |
www.example.com/#section_.* |
https://www.example.com/#section_123 |
Keine |
Unterstützte Datentypen
UriDeepLinkMatcher unterstützt die Deserialisierung von URI-Argumenten in primitive Typen, Enums, Sammlungen und benutzerdefinierte Objekte. Die Serialisierung lässt sich in zwei Kategorien unterteilen:
- Standardserialisierung: Verwendet
kotlinx.serializationfür die Deserialisierung in:- Primitive Typen (
Boolean,Int,Long,Float,Double,Char,Byte,Short) undString - Enums
Set,ListoderArrayvon Primitiven, Strings oder Enums- Verschachtelte
@Serializable-Klassen (deren Attribute in einzelne URI-Platzhalter vereinfacht werden)
- Primitive Typen (
- Benutzerdefinierte Serialisierung mit
DeepLinkSerializer: Konvertiert zwischen einem einzelnenStringund benutzerdefinierten Objekten, externen Typen (z. B.java.time.LocalDate) oder benutzerdefinierten Sammlungen.
Standardserialisierung
UriDeepLinkMatcher funktioniert sofort für Standardtypen und vereinfachte Strukturen, ohne dass benutzerdefinierte Serializer-Implementierungen erforderlich sind.
Primitive und Strings
UriDeepLinkMatcher decodiert automatisch primitive Typen (Boolean, Int, Long, Float, Double, Char, Byte, Short) und 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
Enum-Werte werden unter Berücksichtigung der Groß-/Kleinschreibung mit den Namen der Enum-Elemente abgeglichen:
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)
Wiederholte Abfragesammlungen
Suchparameter mit wiederholten Schlüsseln (z. B. ?id=10&id=20) werden automatisch in List<T>, Set<T> oder Array<T> deserialisiert, wobei T ein primitiver Typ, String oder ein Enum ist:
@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))
Verschachtelte @Serializable-Klassen
Wenn ein NavKey ein Attribut enthält, dessen Typ eine andere @Serializable-Klasse ist, werden die Attribute von UriDeepLinkMatcher vereinfacht, sodass jedes Attribut der verschachtelten Klasse direkt einem einzelnen URI-Parameter mit demselben Namen zugeordnet wird:
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))
Benutzerdefinierte Serialisierung mit DeepLinkSerializer
Wenn Sie benutzerdefinierte Objekte (z. B. Filter(key = "brand", value =
"pixel")), externe Typen (z. B. java.time.LocalDate) oder benutzerdefinierte Strings mit Trennzeichen (z. B. CSV) deserialisieren möchten, erweitern Sie DeepLinkSerializer<T>.
DeepLinkSerializer<T> ist eine abstrakte KSerializer<T>, die zwischen String und T konvertiert:
abstract class DeepLinkSerializer<T : Any> : KSerializer<T> {
abstract val serialName: String
abstract fun deserialize(value: String): T
abstract fun serialize(value: T): String
}
Sehen wir uns zum Beispiel die Definitionen von Filter und FilterSerializer an, die in den folgenden Snippets verwendet werden:
@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}" }
Einzelne benutzerdefinierte Objekte
Wenn Sie ein Objekt aus einem einzelnen URI-Parameterstring (z. B. ?filter=brand:google) decodieren möchten, annotieren Sie das Attribut mit @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"))
Benutzerdefinierte Objekte in wiederholten Abfrageparametern
Wenn Sie wiederholte Suchparameter in eine Sammlung benutzerdefinierter Objekte (List<T>, Set<T> oder Array<T>) deserialisieren möchten, implementieren Sie DeepLinkSerializer<T> für den Elementtyp T und annotieren Sie das Typargument des Attributs mit @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")))
Durch Trennzeichen getrennte Sammlungen in einzelnen Parametern
Wenn Sie kommagetrennte oder benutzerdefinierte Werte (z. B. ?ids=1,2,3) in eine Sammlung parsen möchten, implementieren Sie DeepLinkSerializer für den gesamten Sammlungstyp und annotieren Sie das Attribut mit @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))
Argumentvalidierung und Abgleichsergebnisse
UriDeepLinkMatcher unterscheidet zwischen Nichtübereinstimmungen (gibt null zurück, sodass andere Abgleiche versucht werden können) und nicht unterstützten Konfigurationen (löst eine Ausnahme aus).
Abweichungen
Eine Nichtübereinstimmung tritt auf, wenn ein eingehender Anfrage-URI die Muster- oder Typanforderungen nicht erfüllt:
- Fehlende erforderliche Parameter: Nicht-nullable Schlüsselattribute ohne Standardwerte, deren entsprechende URI-Parameter in der Anfrage-URI fehlen.
- Fehler beim Parsen von Typen: Extrahierte Argumentwerte, die nicht in den erwarteten Attributtyp geparst werden können (z. B.
"abc"für einInt-Attribut).
Wenn eine Abweichung auftritt, gibt UriDeepLinkMatcher.match den Wert null zurück, sodass nachfolgende Abgleiche ausgewertet werden können.
Betrachten Sie eine Schlüsselklasse und einen Matcher, die mit Standardwerten, verschachtelten Objekten und Enums konfiguriert sind:
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>() )
In der folgenden Tabelle sind die Übereinstimmungsergebnisse für verschiedene Anfrage-URIs aufgeführt:
| Anfrage-URI | Decodierungsergebnis | Spielergebnis |
|---|---|---|
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE |
Erfolg (alle Parameter angegeben) | UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE))) |
https://www.example.com/map/paris?style=dark |
Success (zoom ist standardmäßig 12, layer ist standardmäßig STANDARD) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map/paris?zoom=&style=dark |
Erfolg (Für den leeren optionalen Suchparameter wird der Standardwert 12 verwendet) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map?style=dark |
Abweichung (Erforderlicher Parameter location fehlt) |
null |
https://www.example.com/map/paris?zoom=close&style=dark |
Abweichung ("close" ist kein Int) |
null |
https://www.example.com/map/paris?style=dark&layer=HYBRID |
Keine Übereinstimmung ("HYBRID" ist nicht in der Aufzählung enthalten) |
null |
Nicht unterstützte Konfigurationen
Wenn Ihre Schlüsselklasse nicht unterstützte Datentypen enthält, löst UriDeepLinkMatcher während des Abgleichs eine Ausnahme aus, anstatt null zurückzugeben.
- Maps und mehrdimensionale Sammlungen:
UriDeepLinkMatcherunterstützt nur eindimensionale Sammlungen von Primitiven, Strings, Enums oder benutzerdefinierten Typen, die mit einemDeepLinkSerializerannotiert sind.Map-Typen lösen einenIllegalArgumentExceptionaus, während verschachtelte Sammlungen (z. B.List<List<String>>) einenSerializationExceptionauslösen. - Nicht annotierte benutzerdefinierte Objektsammlungen: Sammlungen benutzerdefinierter Typen (z. B.
List<Filter>) lösen eineSerializationExceptionaus, es sei denn, der Elementtyp ist mit einerDeepLinkSerializerannotiert. - Nicht vereinfachte verschachtelte Klassen: Verschachtelte
@Serializable-Klassen können ohneDeepLinkSerializernicht einem einzelnen Platzhalter (z. B.?user={user}) zugeordnet werden.
// 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
UriMatchResult – Vergleich
UriMatchResult-Instanzen werden anhand der folgenden Kriterien in der angegebenen Reihenfolge bewertet:
- MatchResult-Typ:
UriMatchResulthat einen höheren Rang als andereMatchResult-Typen. - Genauer Pfad: Literale Pfadübereinstimmungen werden höher eingestuft als Platzhalter- oder Platzhalterübereinstimmungen.
- Anzahl der Pfadargumente: Übereinstimmungen mit mehr Pfadargumenten werden höher eingestuft.
- Vorhandensein von Argumenten: Übereinstimmungen, die Argumente erfassen, werden höher eingestuft als solche, die dies nicht tun.
- Gesamtanzahl der Argumente: Die Gesamtzahl der Argumente (Pfad, Abfrage, Fragment) ist das letzte Kriterium.
UriDeepLinkMatcher anpassen
UriDeepLinkMatcher ist eine open-Klasse, die Sie als Unterklasse verwenden können, um das Verhalten für den URI-Abgleich und die Argumentextraktion anzupassen:
matchRequest: Übereinstimmender Einstiegspunkt auf oberster Ebene für eine eingehendeDeepLinkRequest. Überschreiben Sie dies, um Anfrage-Extras zu prüfen oder benutzerdefinierte Vorbedingungen vor dem URI-Abgleich anzuwenden.matchUri: Gleicht denDeepLinkUrimit dem konfigurierten Muster ab. Überschreiben Sie dies, um eingehende URIs abzufangen und zu normalisieren (z. B. dynamische Subdomains oder Legacy-Pfadformate neu zu schreiben), bevor Siesuper.matchUriaufrufen.matchArguments: Deserialisiert die extrahierten Pfad-, Abfrage- und Fragmentargumentzuordnungen mithilfe des angegebenenserializerin eine Navigationsschlüsselinstanz. Überschreiben Sie dies, um dynamische Werte einzufügen oder Argumente vor der Schlüsselinstanziierung zu transformieren.
Im folgenden Beispiel wird gezeigt, wie Sie eine abgeleitete Klasse von UriDeepLinkMatcher erstellen, um Legacy-URL-Pfadpräfixe vor dem Abgleich zu normalisieren:
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) } }