name: qs-kotlin description: Use this skill whenever a user wants to install, configure, troubleshoot, or write Kotlin, Java, JVM, or Android application code for encoding and decoding nested query strings with the qs-kotlin package. This skill helps produce practical QS.decode, QS.encode, decode, encode, toQueryMap, and toQueryString snippets, choose DecodeOptions and EncodeOptions, explain option tradeoffs, and avoid qs-kotlin edge-case pitfalls around lists, dot notation, duplicates, null handling, charset sentinels, depth limits, Java interop, Android coordinates, and untrusted input.
qs-kotlin Usage Assistant
Help users parse and build query strings with the Kotlin/JVM qs-kotlin
package and the Android qs-kotlin-android wrapper.
Focus on user application code and interoperability outcomes, not repository
maintenance.
Start With Inputs
Before producing a final snippet, collect only the missing details that change the code:
- Runtime: Kotlin/JVM, Android, Java, tests, backend code, or generated example.
- Direction: decode an incoming query string, encode Kotlin or Java data, or normalize query-string handling around an existing URL/request object.
- The actual query string or data structure when available.
- Target API convention for lists: indexed brackets, empty brackets, repeated keys, or comma-separated values.
- Whether the query may include a leading
?, dot notation, literal dots in keys, duplicate keys, custom delimiters, comma-separated lists,nullflags, ISO-8859-1/legacy charset behavior, Android AAR requirements, or untrusted user input.
Do not over-ask when the desired behavior is obvious. State assumptions in the answer and give the user a concrete snippet they can paste.
Installation
Use the core JVM artifact for normal Kotlin/JVM and most Android projects:
dependencies {
implementation("io.github.techouse:qs-kotlin:<version>")
}
Use the Android AAR wrapper when the project specifically wants an AAR coordinate and Android Gradle Plugin metadata:
dependencies {
implementation("io.github.techouse:qs-kotlin-android:<version>")
}
The library targets Java 17. Android apps with minSdk < 26 that use
java.time transitively may need core library desugaring in the app module.
Public API
Prefer package-level functions in Kotlin:
import io.github.techouse.qskotlin.decode
import io.github.techouse.qskotlin.encode
import io.github.techouse.qskotlin.models.DecodeOptions
import io.github.techouse.qskotlin.models.EncodeOptions
For Java, use the generated QS facade and Java-friendly builders:
import io.github.techouse.qskotlin.QS;
import io.github.techouse.qskotlin.models.DecodeOptions;
import io.github.techouse.qskotlin.models.EncodeOptions;
Kotlin extension helpers are also available:
import io.github.techouse.qskotlin.toQueryMap
import io.github.techouse.qskotlin.toQueryString
val params = "a[b]=c".toQueryMap()
val query = mapOf("a" to mapOf("b" to "c")).toQueryString()
Base Patterns
Decode a query string into nested Kotlin values:
import io.github.techouse.qskotlin.decode
val params =
decode("a[b][c]=d&tags[]=kotlin&tags[]=android")
check(
params ==
mapOf(
"a" to mapOf("b" to mapOf("c" to "d")),
"tags" to listOf("kotlin", "android"),
)
)
Encode nested Kotlin values into a query string:
import io.github.techouse.qskotlin.encode
val query =
encode(
mapOf(
"a" to mapOf("b" to mapOf("c" to "d")),
"tags" to listOf("kotlin", "android"),
)
)
check(query == "a%5Bb%5D%5Bc%5D=d&tags%5B0%5D=kotlin&tags%5B1%5D=android")
Java callers should prefer builders for options:
import io.github.techouse.qskotlin.QS;
import io.github.techouse.qskotlin.enums.ListFormat;
import io.github.techouse.qskotlin.models.EncodeOptions;
import java.util.List;
import java.util.Map;
String query =
QS.encode(
Map.of("tags", List.of("kotlin", "android")),
EncodeOptions.builder().listFormat(ListFormat.REPEAT).build());
assert query.equals("tags=kotlin&tags=android");
Decode Recipes
Use these options with decode(query, DecodeOptions(...)) in Kotlin or
QS.decode(query, DecodeOptions.builder()...build()) in Java:
- Leading question mark:
ignoreQueryPrefix = true. - Dot notation such as
a.b=c:allowDots = true. - Double-encoded literal dots in keys such as
name%252Eobj.first=John:decodeDotInKeys = true. - Duplicate keys:
duplicates = Duplicates.COMBINEkeeps all values as a list; useDuplicates.FIRSTorDuplicates.LASTto collapse plain duplicate keys. Bracket-list keys such asitems[]always combine. - Bracket lists: enabled by default; set
parseLists = falseto treat list syntax as map keys. - Empty list tokens such as
foo[]:allowEmptyLists = true. - Large or sparse list indices: default
listLimitis20; indices greater than or equal to the limit become map keys. A negativelistLimitforces list values to map overflow, or throws whenthrowOnLimitExceeded = true. - Comma-separated values such as
a=b,c:comma = true. - Object/scalar conflicts such as
a[b]=c&a=d:strictMerge = truewraps the conflict in a list; setstrictMerge = falsefor legacy key-as-true behavior. - Tokens without
=asnull:strictNullHandling = true. - Custom delimiters:
delimiter = Delimiter.SEMICOLON,delimiter = StringDelimiter(";"), ordelimiter = RegexDelimiter("[;,]"). - Legacy charset input:
charset = StandardCharsets.ISO_8859_1; usecharsetSentinel = truewhen a form may includeutf8=...to signal the real charset. - HTML numeric entities:
interpretNumericEntities = true, usually with ISO-8859-1 or charset sentinel handling. - Untrusted input: keep
depth,parameterLimit, andlistLimitbounded; usestrictDepth = trueandthrowOnLimitExceeded = truewhen callers need hard failures instead of soft limiting.
Example for a request query:
import io.github.techouse.qskotlin.decode
import io.github.techouse.qskotlin.enums.Duplicates
import io.github.techouse.qskotlin.models.DecodeOptions
val params =
decode(
"?filter.status=open&tag=kotlin&tag=android",
DecodeOptions(
ignoreQueryPrefix = true,
allowDots = true,
duplicates = Duplicates.COMBINE,
),
)
check(params == mapOf("filter" to mapOf("status" to "open"), "tag" to listOf("kotlin", "android")))
Encode Recipes
Use these options with encode(data, EncodeOptions(...)) in Kotlin or
QS.encode(data, EncodeOptions.builder()...build()) in Java:
- List style defaults to
ListFormat.INDICES:tags%5B0%5D=kotlin&tags%5B1%5D=android. - Empty brackets:
listFormat = ListFormat.BRACKETS. - Repeated keys:
listFormat = ListFormat.REPEAT. - Comma-separated values:
listFormat = ListFormat.COMMA. - Single-item comma lists that must round-trip as lists:
commaRoundTrip = true. - Drop
nullitems before comma-joining lists:commaCompactNulls = true. - Dot notation for nested maps:
allowDots = true. - Literal dots in keys:
encodeDotInKeys = true; setallowDots = truewhen nested paths should use dot notation. - Add a leading
?:addQueryPrefix = true. - Custom pair delimiter:
delimiter = Delimiter.SEMICOLONordelimiter = StringDelimiter(";"). - Preserve readable bracket/dot keys while encoding values:
encodeValuesOnly = true. - Disable percent encoding entirely for debugging or documented examples:
encode = false. - Emit
nullwithout=:strictNullHandling = true. - Omit
nullkeys:skipNulls = true. - Emit empty lists as
foo[]:allowEmptyLists = true. - Omit selected values: use
skipNulls = truefornullvalues, returnnullfrom aFunctionFilter, or remove those entries before callingencode. - Legacy form spaces as
+:format = Format.RFC1738; the default isFormat.RFC3986, which emits spaces as%20. - Legacy charset output:
charset = StandardCharsets.ISO_8859_1; usecharsetSentinel = trueto prepend theutf8=...sentinel. - Custom behavior: use
encoder,dateSerializer,sort, orfilterwhen the target API needs special scalar encoding, date formatting, stable key order, or selected fields.
Example for an API that expects repeated keys:
import io.github.techouse.qskotlin.encode
import io.github.techouse.qskotlin.enums.ListFormat
import io.github.techouse.qskotlin.models.EncodeOptions
val query =
encode(
mapOf(
"q" to "query strings",
"tag" to listOf("kotlin", "android"),
),
EncodeOptions(
listFormat = ListFormat.REPEAT,
addQueryPrefix = true,
),
)
check(query == "?q=query%20strings&tag=kotlin&tag=android")
Java Interop
Use builders from Java to avoid long Kotlin data-class constructors:
import io.github.techouse.qskotlin.QS;
import io.github.techouse.qskotlin.enums.Duplicates;
import io.github.techouse.qskotlin.models.DecodeOptions;
import java.util.List;
import java.util.Map;
Map<String, Object> params =
QS.decode(
"?tag=kotlin&tag=android",
DecodeOptions.builder()
.ignoreQueryPrefix(true)
.duplicates(Duplicates.COMBINE)
.build());
assert params.equals(Map.of("tag", List.of("kotlin", "android")));
Use Java-friendly functional interfaces for custom callbacks:
JDecoderfor decode customization.JValueEncoderfor encode customization.JDateSerializerforLocalDateTimeserialization.- Java
Comparatorinstances throughEncodeOptions.builder().sort(...).
Combinations To Check
Warn or adjust before giving code for these cases:
DecodeOptions(decodeDotInKeys = true, allowDots = false)is invalid.parameterLimitmust be positive, orInt.MAX_VALUEfor effectively unlimited parsing.throwOnLimitExceeded = trueturns parameter and list limit violations intoIndexOutOfBoundsException; without it, parameter parsing stops at the limit and list overflows fall back to maps.strictDepth = truethrows on well-formed depth overflow; with the defaultfalse, the remainder beyonddepthis kept as a trailing key segment.- Built-in charset handling supports only
StandardCharsets.UTF_8andStandardCharsets.ISO_8859_1; other encodings require a customencoderordecoder. EncodeOptions.encoderis ignored whenencode = false.- Combining
encodeValuesOnly = trueandencodeDotInKeys = trueencodes only dots in keys; values remain otherwise unchanged. DecodeOptions.commaparses simple comma-separated values, but does not decode nested map syntax such asa={b:1},{c:d}.encode(null), scalar roots, empty maps, and empty containers generally produce an empty string.- The JDK and many web frameworks flatten duplicates or nested query syntax.
Prefer
decodeorQS.decodeon the raw query string when qs-style nested or repeated values matter.
Response Shape
For code-generation requests, answer with:
- A short statement of assumptions, especially language, artifact coordinate, list format, null handling, charset, prefix handling, and whether input is trusted.
- One concrete Kotlin or Java snippet using
decode,encode,toQueryMap,toQueryString, orQS. - A brief explanation of only the options used.
- A small verification example, such as an expected map, expected query string,
JUnit assertion, Kotest assertion, or
check(...).
Keep snippets application-oriented. Prefer public API imports from
io.github.techouse.qskotlin; do not ask users to import from
io.github.techouse.qskotlin.internal.