계층적 URI를 패턴과 일치시키고 인수를 추출하려면
UriDeepLinkMatcher를 사용하세요. 일치하는 인수를 키 클래스로 역직렬화하기 위해 kotlinx.serialization에 의존합니다.
UriDeepLinkMatcher를 만들려면 패턴 DeepLinkUri와
상응하는 키의 직렬화기를 제공하세요.
@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")
비계층적 URI 또는 커스텀 스키마 (예: tel:)의 경우
커스텀 딥 링크 일치기 만들기를 참고하세요.
지원되는 일치 패턴
UriDeepLinkMatcher 는 스키마, 권한, 경로, 쿼리, 프래그먼트의 5가지 구성요소를 기반으로 URI를 일치시킵니다. 다음 섹션에서는 각 구성요소에 지원되는 패턴 구문, 인수 자리표시자, 일치 규칙을 설명합니다.
스키마 일치
URI 패턴에 스키마가 없으면 http와 https가 모두 일치합니다.
특정 스키마를 일치시키려면 패턴에 포함하세요. 예외적으로 패턴의
http 스키마는 http 및 https 요청 URI와 모두 일치하는 반면
https 패턴의 https 요청만 일치합니다.
| 패턴 URI | 요청 URI | 일치 |
|---|---|---|
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 |
✅ |
권한 일치
UriDeepLinkMatcher 는 URI 권한 (호스트 및 선택적 포트)에 대해 대소문자를 구분하지 않는 일치검색을 실행합니다. 자리표시자 또는 와일드 카드는 권한에서 지원되지 않으며 인수가 추출되지 않습니다.
| 패턴 URI | 요청 URI | 일치 |
|---|---|---|
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 |
❌ |
경로 일치
다음 경로 패턴이 지원됩니다.
| 패턴 URI | 요청 URI | 일치 | 추출된 인수 |
|---|---|---|---|
www.example.com/users |
https://www.example.com/users |
✅ | 없음 |
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: "" (빈 문자열) |
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 |
✅ | 없음 |
www.example.com/users |
https://www.example.com/users/ |
❌ (후행 슬래시로 인해 추가 세그먼트가 생성됨) | 해당 사항 없음 |
쿼리 일치
요청 URI의 쿼리 매개변수 순서는 패턴 URI의 순서와 일치하지 않아도 됩니다. 또한 요청 URI에는 있지만 패턴 URI에는 없는 매개변수는 무시됩니다.
다음 쿼리 매개변수 패턴이 지원됩니다.
| 패턴 URI | 요청 URI | 추출된 인수 |
|---|---|---|
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: "" (빈 문자열) |
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" |
프래그먼트 일치
다음 프래그먼트 패턴 유형이 지원됩니다.
| 패턴 URI | 요청 URI | 추출된 인수 |
|---|---|---|
www.example.com/#section1 |
https://www.example.com/#section1 |
없음 |
www.example.com/#section_{id} |
https://www.example.com/#section_123 |
id: "123" |
www.example.com/#section_.* |
https://www.example.com/#section_123 |
없음 |
지원되는 데이터 유형
UriDeepLinkMatcher 는 URI 인수를 기본 유형, 열거형, 컬렉션, 커스텀 객체로 역직렬화하는 것을 지원합니다. 직렬화는 두 가지 카테고리로 나뉩니다.
- 표준 직렬화:
kotlinx.serialization을 사용하여 다음으로 역직렬화합니다.- 기본 요소 (
Boolean,Int,Long,Float,Double,Char,Byte,Short) 및String - 열거형
- 기본 요소, 문자열 또는 열거형의
Set,List또는Array - 중첩된
@Serializable클래스 (속성이 개별 URI 자리표시자로 평면화됨)
- 기본 요소 (
DeepLinkSerializer를 사용한 커스텀 직렬화: 단일String과 커스텀 객체, 외부 유형 (java.time.LocalDate등) 또는 커스텀 구분 컬렉션 간에 변환합니다.
표준 직렬화
UriDeepLinkMatcher 는 커스텀 직렬화기 구현 없이 표준 유형 및 평면화된 구조에 바로 사용할 수 있습니다.
기본 요소 및 문자열
UriDeepLinkMatcher 는 기본 유형 (Boolean, Int, Long, Float, Double, Char, Byte, Short) 및 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 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)
반복되는 쿼리 컬렉션
반복되는 키가 있는 쿼리 매개변수 (예: ?id=10&id=20)는 자동으로
List<T>, Set<T>, 또는 Array<T>로 역직렬화됩니다. 여기서 T는 기본
유형, String 또는 열거형입니다.
@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))
중첩된 @Serializable 클래스
NavKey에 유형이 다른 @Serializable 클래스인 속성이 포함된 경우 UriDeepLinkMatcher는 속성을 평면화하므로 중첩된 클래스의 각 속성이 동일한 이름의 개별 URI 매개변수에 직접 매핑됩니다.
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))
DeepLinkSerializer를 사용한 커스텀 직렬화
커스텀 객체 (Filter(key = "brand", value =
"pixel") 등), 외부 유형 (java.time.LocalDate 등) 또는 커스텀 구분
문자열 (쉼표로 구분된 값 등)을 역직렬화하려면 DeepLinkSerializer<T>를 확장하세요.
DeepLinkSerializer<T>는 KSerializer<T> 간에 변환하는 추상
String 및 T입니다.
abstract class DeepLinkSerializer<T : Any> : KSerializer<T> {
abstract val serialName: String
abstract fun deserialize(value: String): T
abstract fun serialize(value: T): String
}
예를 들어 다음 스니펫에 사용되는 Filter 및 FilterSerializer 정의를 살펴보세요.
@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}" }
단일 커스텀 객체
단일 URI 매개변수 문자열 (예: ?filter=brand:google)에서 객체를 디코딩하려면 속성에 @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"))
반복되는 쿼리 매개변수의 커스텀 객체
반복되는 쿼리 매개변수를 커스텀 객체 컬렉션
(List<T>, Set<T>, 또는 Array<T>)으로 역직렬화하려면DeepLinkSerializer<T>
요소 유형 T에 대해 구현하고 속성의 유형 인수에
@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")))
단일 매개변수의 구분 컬렉션
쉼표로 구분된 값 또는 커스텀 구분 값 (예: ?ids=1,2,3)을 컬렉션으로 파싱하려면 전체 컬렉션 유형 에 대해 DeepLinkSerializer를 구현하고 속성에 @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))
인수 유효성 검사 및 일치 결과
UriDeepLinkMatcher 는 불일치 (다른 일치기를 시도할 수 있도록 null 반환)와 지원되지 않는 구성 (예외 발생)을 구분합니다.
불일치
수신 요청 URI가 패턴 또는 유형 요구사항을 충족하지 않으면 불일치가 발생합니다.
- 필수 매개변수 누락: 기본값이 없는 null이 허용되지 않는 키 속성으로, 상응하는 URI 매개변수가 요청 URI에 없습니다.
- 유형 파싱 실패: 예상 속성 유형으로 파싱할 수 없는 추출된 인수 값 (예:
"abc"속성의Int).
불일치가 발생하면 UriDeepLinkMatcher.match가 null을 반환하여 후속 일치기를 평가할 수 있도록 합니다.
기본값, 중첩된 객체, 열거형으로 구성된 키 클래스와 일치기를 고려하세요.
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>() )
다음 표에서는 다양한 요청 URI의 일치 결과를 보여줍니다.
| 요청 URI | 디코딩 결과 | 일치 결과 |
|---|---|---|
https://www.example.com/map/paris?zoom=15&style=dark&layer=SATELLITE |
성공 (모든 매개변수 제공됨) | UriMatchResult(MapKey("paris", 15, LayerOptions("dark", MapLayer.SATELLITE))) |
https://www.example.com/map/paris?style=dark |
성공 (zoom 기본값은 12, layer 기본값은 STANDARD) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map/paris?zoom=&style=dark |
성공 (빈 선택적 쿼리 매개변수가 기본값 12를 사용함) |
UriMatchResult(MapKey("paris", 12, LayerOptions("dark", MapLayer.STANDARD))) |
https://www.example.com/map?style=dark |
불일치 (필수 location 매개변수 누락) |
null |
https://www.example.com/map/paris?zoom=close&style=dark |
불일치 ("close"는 Int가 아님) |
null |
https://www.example.com/map/paris?style=dark&layer=HYBRID |
불일치 ("HYBRID"가 열거형에 없음) |
null |
지원되지 않는 구성
키 클래스에 지원되지 않는 데이터 유형이 포함된 경우 UriDeepLinkMatcher는 null을 반환하는 대신 일치 중에 예외를 발생시킵니다.
- 지도 및 다차원 컬렉션:
UriDeepLinkMatcher는 기본 요소, 문자열, 열거형 또는DeepLinkSerializer로 주석 처리된 커스텀 유형의 1차원 컬렉션만 지원합니다.Map유형은IllegalArgumentException을 발생시키는 반면 중첩된 컬렉션 (List<List<String>>등)은SerializationException을 발생시킵니다. - 주석 처리되지 않은 커스텀 객체 컬렉션: 요소 유형이
DeepLinkSerializer로 주석 처리되지 않은 경우 커스텀 유형의 컬렉션 (등List<Filter>)은SerializationException을 발생시킵니다. - 평면화되지 않은 중첩된 클래스: 중첩된
@Serializable클래스는 단일 자리표시자 (예:?user={user})에DeepLinkSerializer없이 매핑할 수 없습니다.
// 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 비교
UriMatchResult 인스턴스는 다음 기준을 순서대로 사용하여 순위가 지정됩니다.
- MatchResult 유형:
UriMatchResult는 다른MatchResult유형보다 순위가 높습니다. - 정확한 경로: 리터럴 경로 일치는 자리표시자 또는 와일드 카드 일치보다 순위가 높습니다.
- 경로 인수 개수: 경로 인수가 더 많은 일치가 순위가 높습니다.
- 인수 존재: 인수를 캡처하는 일치가 그렇지 않은 일치보다 순위가 높습니다.
- 총 인수 개수: 총 인수 개수 (경로, 쿼리, 프래그먼트)는 최종 동점 상황을 해결합니다.
UriDeepLinkMatcher 맞춤설정
UriDeepLinkMatcher 는 URI 일치 및 인수 추출 동작을 맞춤설정하기 위해 서브클래스화할 수 있는 open 클래스입니다.
matchRequest: 수신DeepLinkRequest의 최상위 일치 진입점입니다. 요청 추가 항목을 검사하거나 URI 일치 전에 커스텀 사전 조건을 적용하려면 이를 재정의하세요.matchUri: 구성된 패턴과DeepLinkUri를 일치시킵니다.super.matchUri를 호출하기 전에 수신 URI를 가로채고 정규화 (예: 동적 하위 도메인 또는 기존 경로 형식 재작성)하려면 이를 재정의하세요.matchArguments: 제공된serializer를 사용하여 추출된 경로, 쿼리, 프래그먼트 인수 지도를 탐색 키 인스턴스로 역직렬화합니다. 키 인스턴스화 전에 동적 값을 삽입하거나 인수를 변환하려면 이를 재정의하세요.
다음 예에서는 일치시키기 전에 기존 URL 경로 접두사를 정규화하기 위해 UriDeepLinkMatcher를 서브클래스화하는 방법을 보여줍니다.
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) } }