Aby dopasować hierarchiczne identyfikatory URI do wzorców i wyodrębnić argumenty, użyj UriDeepLinkMatcher. Do deserializacji dopasowanych argumentów do klas kluczy używa kotlinx.serialization.
Aby utworzyć UriDeepLinkMatcher, podaj wzorzec DeepLinkUri i serializator dla odpowiedniego klucza:
@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")
W przypadku identyfikatorów URI bez hierarchii lub schematów niestandardowych (takich jak tel:) zapoznaj się z artykułem Tworzenie niestandardowych dopasowań precyzyjnych linków.
Obsługiwane wzorce dopasowania
UriDeepLinkMatcher dopasowuje identyfikatory URI na podstawie ich 5 komponentów: schematu, urzędu, ścieżki, zapytania i fragmentu. W sekcjach poniżej opisano obsługiwaną składnię wzorców, obiekty zastępcze argumentów i reguły dopasowywania dla każdego komponentu.
Dopasowanie schematu
Jeśli we wzorcu identyfikatora URI nie ma schematu, dopasowywane są zarówno http, jak i https.
Aby dopasować konkretny schemat, uwzględnij go we wzorcu. Wyjątkiem jest schemat http we wzorcu, który pasuje zarówno do identyfikatorów URI żądania http, jak i https, podczas gdy schemat https we wzorcu pasuje tylko do żądań https.
| Identyfikator URI wzorca | Identyfikator URI żądania | Dopasowanie |
|---|---|---|
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 |
✅ |
Dopasowywanie urzędów
UriDeepLinkMatcher wykonuje dopasowanie ścisłe, w którym wielkość liter nie jest rozróżniana, w identyfikatorze URI (host i opcjonalny port). Symbole zastępcze i wieloznaczne nie są obsługiwane w elemencie authority i nie są wyodrębniane żadne argumenty:
| Identyfikator URI wzorca | Identyfikator URI żądania | Dopasowanie |
|---|---|---|
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 |
❌ |
Dopasowywanie ścieżek
Obsługiwane są te wzorce ścieżek:
| Identyfikator URI wzorca | Identyfikator URI żądania | Dopasowanie | Wyodrębnione argumenty |
|---|---|---|---|
www.example.com/users |
https://www.example.com/users |
✅ | Brak |
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: "" (pusty ciąg znaków) |
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 |
✅ | Brak |
www.example.com/users |
https://www.example.com/users/ |
❌ (ukośnik na końcu tworzy dodatkowy segment) | Nie dotyczy |
Dopasowywanie zapytań
Kolejność parametrów zapytania w identyfikatorze URI żądania nie musi być zgodna z kolejnością we wzorcu identyfikatora URI. Dodatkowo parametry obecne w identyfikatorze URI żądania, ale nie we wzorcu identyfikatora URI, są ignorowane.
Obsługiwane są te wzorce parametrów zapytania:
| Identyfikator URI wzorca | Identyfikator URI żądania | Wyodrębnione argumenty |
|---|---|---|
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: "" (pusty ciąg znaków) |
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" |
Dopasowywanie fragmentów
Obsługiwane są te typy wzorców fragmentów:
| Identyfikator URI wzorca | Identyfikator URI żądania | Wyodrębnione argumenty |
|---|---|---|
www.example.com/#section1 |
https://www.example.com/#section1 |
Brak |
www.example.com/#section_{id} |
https://www.example.com/#section_123 |
id: "123" |
www.example.com/#section_.* |
https://www.example.com/#section_123 |
Brak |
Obsługiwane typy danych
UriDeepLinkMatcher obsługuje deserializację argumentów identyfikatora URI na typy podstawowe, typy wyliczeniowe, kolekcje i obiekty niestandardowe. Publikacja w odcinkach dzieli się na 2 kategorie:
- Standardowa serializacja: używa
kotlinx.serializationdo deserializacji do:- Typy podstawowe (
Boolean,Int,Long,Float,Double,Char,Byte,Short) iString - Wartości w polu enum
Set,ListlubArrayelementów podstawowych, ciągów znaków lub typów wyliczeniowych- Zagnieżdżone klasy
@Serializable(których właściwości są spłaszczone do poszczególnych obiektów zastępczych identyfikatorów URI)
- Typy podstawowe (
- Niestandardowa serializacja za pomocą
DeepLinkSerializer: konwertuje między pojedynczym obiektemStringa obiektami niestandardowymi, typami zewnętrznymi (takimi jakjava.time.LocalDate) lub kolekcjami z niestandardowymi separatorami.
Standardowa serializacja
UriDeepLinkMatcher działa od razu po uruchomieniu w przypadku standardowych typów i spłaszczonych struktur bez konieczności implementacji niestandardowych serializatorów.
Elementy podstawowe i ciągi znaków
UriDeepLinkMatcher automatycznie dekoduje typy podstawowe (Boolean, Int, Long, Float, Double, Char, Byte, Short) i 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)
Wartości w polu enum
Wartości typu wyliczeniowego są dopasowywane do nazw elementów typu wyliczeniowego z uwzględnieniem wielkości liter:
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)
Powtarzające się kolekcje zapytań
Parametry zapytania z powtarzającymi się kluczami (takimi jak ?id=10&id=20) są automatycznie deserializowane do List<T>, Set<T> lub Array<T>, gdzie T jest typem podstawowym, String lub typem wyliczeniowym:
@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))
Zagnieżdżone klasy @Serializable
Gdy NavKey zawiera właściwość, której typem jest inna klasa @Serializable, UriDeepLinkMatcher spłaszcza jej właściwości, tak aby każda właściwość klasy zagnieżdżonej była mapowana bezpośrednio na pojedynczy parametr identyfikatora URI o tej samej nazwie:
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))
Niestandardowa serializacja za pomocą DeepLinkSerializer
Aby deserializować obiekty niestandardowe (takie jak Filter(key = "brand", value =
"pixel")), typy zewnętrzne (takie jak java.time.LocalDate) lub niestandardowe ciągi znaków rozdzielane separatorami (takie jak wartości rozdzielane przecinkami), rozszerz DeepLinkSerializer<T>.
DeepLinkSerializer<T> to abstrakcyjna klasa KSerializer<T>, która konwertuje między String a T:
abstract class DeepLinkSerializer<T : Any> : KSerializer<T> {
abstract val serialName: String
abstract fun deserialize(value: String): T
abstract fun serialize(value: T): String
}
Weźmy na przykład definicje Filter i FilterSerializer używane w tych fragmentach kodu:
@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}" }
Pojedyncze obiekty niestandardowe
Aby zdekodować obiekt z pojedynczego ciągu parametrów identyfikatora URI (np. ?filter=brand:google), dodaj do właściwości adnotację @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"))
Obiekty niestandardowe w powtarzających się parametrach zapytania
Aby zdeserializować powtarzające się parametry zapytania do kolekcji obiektów niestandardowych (List<T>, Set<T> lub Array<T>), zaimplementuj DeepLinkSerializer<T> dla typu elementu T i dodaj adnotację do argumentu typu właściwości za pomocą @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")))
Kolekcje rozdzielane w pojedynczych parametrach
Aby przeanalizować wartości rozdzielone przecinkami lub niestandardowymi separatorami (np. ?ids=1,2,3) i utworzyć z nich kolekcję, zaimplementuj DeepLinkSerializer dla całego typu kolekcji i dodaj do właściwości adnotację @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))
Wyniki weryfikacji i dopasowywania argumentów
UriDeepLinkMatcher rozróżnia niezgodności (zwraca null, aby można było wypróbować inne dopasowania) i nieobsługiwane konfiguracje (zgłasza wyjątek).
Niezgodności
Niezgodność występuje, gdy identyfikator URI żądania przychodzącego nie spełnia wymagań dotyczących wzorca lub typu:
- Brak wymaganych parametrów: właściwości klucza, które nie mogą mieć wartości null i nie mają wartości domyślnych, a których odpowiadające im parametry identyfikatora URI nie występują w identyfikatorze URI żądania.
- Błędy analizy typu: wyodrębnione wartości argumentów, których nie można przeanalizować na oczekiwany typ właściwości (np.
"abc"dla właściwościInt).
W przypadku niezgodności UriDeepLinkMatcher.match zwraca null, co umożliwia ocenę kolejnych dopasowań.
Rozważ klasę klucza i dopasowanie skonfigurowane z wartościami domyślnymi, obiektami zagnieżdżonymi i typami wyliczeniowymi:
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>() )
W tabeli poniżej znajdziesz wyniki dopasowania dla różnych identyfikatorów URI żądania:
| Identyfikator URI żądania | Wynik dekodowania | Wynik dopasowania |
|---|---|---|
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE |
Success (podano wszystkie parametry) | UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE))) |
https://www.example.com/map/paris?style=dark |
Sukces (zoom domyślnie 12, layer domyślnie STANDARD) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map/paris?zoom=&style=dark |
Sukces (pusty opcjonalny parametr zapytania używa domyślnej wartości 12) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map?style=dark |
Niezgodność (brak wymaganego parametru location) |
null |
https://www.example.com/map/paris?zoom=close&style=dark |
Niezgodność ("close" nie jest Int) |
null |
https://www.example.com/map/paris?style=dark&layer=HYBRID |
Niezgodność ("HYBRID" nie jest w wyliczeniu) |
null |
Nieobsługiwane konfiguracje
Jeśli klasa klucza zawiera nieobsługiwane typy danych, UriDeepLinkMatcher zgłasza wyjątek podczas dopasowywania, zamiast zwracać null.
- Mapy i kolekcje wielowymiarowe:
UriDeepLinkMatcherobsługuje tylko jednowymiarowe kolekcje elementów podstawowych, ciągów tekstowych, typów wyliczeniowych lub typów niestandardowych z adnotacjąDeepLinkSerializer. TypyMapzwracają błądIllegalArgumentException, a kolekcje zagnieżdżone (takie jakList<List<String>>) – błądSerializationException. - Kolekcje obiektów niestandardowych bez adnotacji: kolekcje typów niestandardowych (takich jak
List<Filter>) zgłaszają błądSerializationException, chyba że typ elementu ma adnotacjęDeepLinkSerializer. - Niespłaszczone klasy zagnieżdżone: klas zagnieżdżonych
@Serializablenie można mapować na pojedynczy obiekt zastępczy (np.?user={user}) bezDeepLinkSerializer.
// 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
Porównanie: UriMatchResult
Instancje UriMatchResult są klasyfikowane według tych kryteriów w podanej kolejności:
- Typ MatchResult:
UriMatchResultma wyższą pozycję niż inne typyMatchResult. - Dokładna ścieżka: dopasowania ścieżki dosłownej mają wyższą pozycję niż dopasowania obiektów zastępczych lub symboli wieloznacznych.
- Liczba argumentów ścieżki: dopasowania z większą liczbą argumentów ścieżki mają wyższą pozycję.
- Obecność argumentów: dopasowania, które zawierają argumenty, mają wyższą pozycję niż te, które ich nie zawierają.
- Łączna liczba argumentów: łączna liczba argumentów (ścieżka, zapytanie, fragment) jest ostatecznym kryterium rozstrzygającym.
Dostosuj UriDeepLinkMatcher
UriDeepLinkMatcher to klasa open, którą możesz rozszerzyć, aby dostosować dopasowywanie identyfikatorów URI i wyodrębnianie argumentów:
matchRequest: punkt wejścia dopasowania najwyższego poziomu dla przychodzącegoDeepLinkRequest. Zastąp tę metodę, aby sprawdzać dodatkowe informacje o żądaniu lub stosować niestandardowe warunki wstępne przed dopasowaniem identyfikatora URI.matchUri: dopasowujeDeepLinkUrido skonfigurowanego wzorca. Zastąp tę metodę, aby przechwytywać i normalizować przychodzące identyfikatory URI (np. przepisywać dynamiczne subdomeny lub starsze formaty ścieżek) przed wywołaniemsuper.matchUri.matchArguments: deserializuje wyodrębnione mapy argumentów ścieżki, zapytania i fragmentu do instancji klucza nawigacji za pomocą podanegoserializer. Zastąp tę metodę, aby wstawić wartości dynamiczne lub przekształcić argumenty przed utworzeniem instancji klucza.
Poniższy przykład pokazuje, jak utworzyć podklasę UriDeepLinkMatcher, aby znormalizować starsze prefiksy ścieżek adresów URL przed dopasowaniem:
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) } }