Verwenden Sie, um hierarchische URIs mit Mustern abzugleichen und Argumente zu extrahieren
UriDeepLinkMatcher. Dabei wird kotlinx.serialization verwendet, um übereinstimmende Argumente in Ihre Schlüsselklassen zu deserialisieren.
Um einen UriDeepLinkMatcher zu erstellen, geben Sie ein DeepLinkUri-Muster 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 Matcher für Deeplinks 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, Argumentplatzhalter und Abgleichsregeln für jede Komponente 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. Ausnahmsweise gleicht ein
http Schema in einem Muster sowohl http als auch https Anfrage-URIs ab, während
https in einem Muster nur https Anfragen abgleicht.
| 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 |
✅ |
Authority-Abgleich
UriDeepLinkMatcher führt einen genauen Abgleich ohne Berücksichtigung der Groß- und Kleinschreibung für die URI-Authority (Host und optionaler Port) durch. Platzhalter oder Wildcards 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 nachgestellte Schrägstrich erstellt ein zusätzliches Segment) | – |
Abfrageabgleich
Die Reihenfolge der Abfrageparameter in der Anfrage-URI muss nicht mit der Reihenfolge in der Muster-URI übereinstimmen. Außerdem werden Parameter, die in der Anfrage-URI, aber nicht in der Muster-URI vorhanden sind, ignoriert.
Die folgenden Muster für Abfrageparameter 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 Typen, Strings oder Enums- Verschachtelte
@Serializable-Klassen (deren Attribute in einzelne URI-Platzhalter umgewandelt 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 umgewandelte Strukturen, ohne dass benutzerdefinierte Serialisierungsimplementierungen erforderlich sind.
Primitive Typen 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 ohne Berücksichtigung der Groß- und 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
Abfrageparameter 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 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, wandelt UriDeepLinkMatcher die Attribute um, 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 (z. B. durch Kommas getrennte Werte) deserialisieren möchten, erweitern Sie DeepLinkSerializer<T>.
DeepLinkSerializer<T> ist ein abstrakter KSerializer<T>, der
zwischen einem 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
}
Betrachten Sie beispielsweise die Filter und FilterSerializer Definitionen, 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 decodieren möchten (z. B. ?filter=brand:google), versehen 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 Abfrageparameter 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 versehen 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")))
Begrenzte 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 versehen 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 Abweichungen (gibt null zurück, damit andere Matcher versucht werden können) und nicht unterstützten Konfigurationen (löst eine Ausnahme aus).
Abweichungen
Eine Abweichung tritt auf, wenn eine eingehende Anfrage-URI nicht den Muster- oder Typanforderungen entspricht:
- Fehlende erforderliche Parameter: Nicht nullable-Schlüsselattribute ohne Standard werte, 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 einIntAttribut).
Wenn eine Abweichung auftritt, gibt UriDeepLinkMatcher.match null zurück, sodass nachfolgende Matcher 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 Abgleichsergebnisse für verschiedene Anfrage-URIs dargestellt:
| Anfrage-URI | Ergebnis der Decodierung | Abgleichsergebnis |
|---|---|---|
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 |
Erfolg (zoom wird standardmäßig auf 12 und layer auf STANDARD gesetzt) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map/paris?zoom=&style=dark |
Erfolg (Leerer optionaler Abfrageparameter verwendet Standardwert 12) |
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 |
Abweichung ("HYBRID" ist nicht im Enum 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 Typen, Strings, Enums oder benutzerdefinierten Typen, die mit einemDeepLinkSerializerversehen sind.Map-Typen lösen eineIllegalArgumentExceptionaus, während verschachtelte Sammlungen (z. B.List<List<String>>) eineSerializationExceptionauslösen. - Nicht annotierte Sammlungen benutzerdefinierter Objekte: Sammlungen benutzerdefinierter Typen (z. B.
List<Filter>) lösen eineSerializationExceptionaus, es sei denn, der Elementtyp ist mit einemDeepLinkSerializerversehen. - Nicht umgewandelte verschachtelte Klassen: Verschachtelte
@SerializableKlassen 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:
UriMatchResultwird höher bewertet als andereMatchResultTypen. - Genauer Pfad: Literale Pfadübereinstimmungen werden höher bewertet als Übereinstimmungen mit Platzhaltern oder Wildcards.
- Anzahl der Pfadargumente: Übereinstimmungen mit mehr Pfadargumenten werden höher bewertet.
- Vorhandensein von Argumenten: Übereinstimmungen, die Argumente erfassen, werden höher bewertet als solche, die keine Argumente erfassen.
- Gesamtzahl der Argumente: Die Gesamtzahl der Argumente (Pfad, Abfrage, Fragment) ist der letzte Entscheidungsfaktor.
UriDeepLinkMatcher anpassen
UriDeepLinkMatcher ist eine open-Klasse, die Sie unterteilen können, um das Verhalten beim URI-Abgleich und bei der Argumentextraktion anzupassen:
matchRequest: Einstiegspunkt für den Abgleich auf oberster Ebene für eine eingehendeDeepLinkRequest. Überschreiben Sie diese Methode, um zusätzliche Anfragen zu prüfen oder benutzerdefinierte Vorbedingungen anzuwenden, bevor der URI-Abgleich erfolgt.matchUri: Gleicht dieDeepLinkUrimit dem konfigurierten Muster ab. Überschreiben Sie diese Methode, um eingehende URIs abzufangen und zu normalisieren (z. B. dynamische Subdomains oder Legacy-Pfadformate neu zu schreiben), bevor Siesuper.matchUriaufrufen.matchArguments: Deserialisiert die extrahierten Argumentzuordnungen für Pfad, Abfrage und Fragment mithilfe des angegebenenserializerin eine Navigationsschlüsselinstanz. Überschreiben Sie diese Methode, um dynamische Werte einzufügen oder Argumente vor der Schlüsselinstanziierung zu transformieren.
Im folgenden Beispiel wird gezeigt, wie Sie eine Unterklasse 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) } }