계층식 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 인수를 기본 유형, enum, 컬렉션, 맞춤 객체로 역직렬화하는 것을 지원합니다. 직렬화는 다음 두 가지 카테고리로 분류됩니다.
- 표준 직렬화:
kotlinx.serialization를 사용하여 다음으로 역직렬화합니다.- 기본 요소 (
Boolean,Int,Long,Float,Double,Char,Byte,Short) 및String - 열거형
Set,List또는 기본 요소, 문자열, enum의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 값은 enum 요소 이름과 대소문자를 구분하여 일치합니다.
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 또는 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))
중첩된 @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>는 String와 T 간에 변환하는 추상 KSerializer<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>)으로 역직렬화하려면 요소 유형 T에 DeepLinkSerializer<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가 패턴 또는 유형 요구사항을 충족하지 않으면 불일치가 발생합니다.
- 필수 매개변수 누락: 해당 URI 매개변수가 요청 URI에 없는 기본값이 없는 null을 허용하지 않는 키 속성입니다.
- 유형 파싱 실패: 예상 속성 유형으로 파싱할 수 없는 추출된 인수 값 (예:
Int속성의"abc")
불일치가 발생하면 UriDeepLinkMatcher.match는 null를 반환하여 후속 일치자를 평가할 수 있습니다.
기본값, 중첩 객체, 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>() )
다음 표는 다양한 요청 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"가 enum에 없음) |
null |
지원되지 않는 구성
키 클래스에 지원되지 않는 데이터 유형이 포함된 경우 UriDeepLinkMatcher는 일치하는 동안 null를 반환하는 대신 예외를 발생시킵니다.
- 지도 및 다차원 컬렉션:
UriDeepLinkMatcher는DeepLinkSerializer로 주석이 달린 프리미티브, 문자열, enum 또는 맞춤 유형의 1차원 컬렉션만 지원합니다.Map유형은IllegalArgumentException를 발생시키고 중첩된 컬렉션 (예:List<List<String>>)은SerializationException를 발생시킵니다. - 주석이 없는 커스텀 객체 컬렉션: 커스텀 유형의 컬렉션 (예:
List<Filter>)은 요소 유형에DeepLinkSerializer주석이 추가되지 않는 한SerializationException를 발생시킵니다. - 평면화되지 않은 중첩 클래스: 중첩된
@Serializable클래스는DeepLinkSerializer없이 단일 자리표시자 (예:?user={user})에 매핑할 수 없습니다.
// 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) } }