A query string encoding and decoding library for Android and Kotlin/JVM. Ported from qs for JavaScript.
https://github.com/techouse/qs-kotlin.git
A query string encoding and decoding library for Android and Kotlin/JVM.
Ported from qs for JavaScript.
This repo provides:
qs-kotlin β the core JVM library (Jar)qs-kotlin-android β a thin Android AAR wrapper that re-exports the same APIIf you only target the JVM (including Android projects that are fine with a plain Jar), just use qs-kotlin. The Android wrapper is provided for teams that prefer an AAR coordinate and AGP metadata.
foo[bar][baz]=qux β { foo: { bar: { baz: "qux" } } }a.b=c) and "."-encoding togglesutf8=β)LocalDateTime/Instant serialization via a pluggable serializerKotlin:
dependencies {
implementation("io.github.techouse:qs-kotlin:<version>")
}
Java (Gradle Groovy DSL):
dependencies {
implementation 'io.github.techouse:qs-kotlin:<version>'
}
Kotlin:
dependencies {
implementation("io.github.techouse:qs-kotlin-android:<version>")
}
Java (Gradle Groovy DSL):
dependencies {
implementation 'io.github.techouse:qs-kotlin-android:<version>'
}
The Android AAR depends on Java 17 APIs. If your appβsminSdk < 26and you usejava.timetransitively, enable core library desugaring in your app:
Kotlin:
android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
isCoreLibraryDesugaringEnabled = true
}
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
}
Java (Gradle Groovy DSL):
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
coreLibraryDesugaringEnabled = true
}
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
}
compileSdk 35, minSdk 25Kotlin:
import io.github.techouse.qskotlin.QS
// Decode
val obj: Map<String, Any?> = QS.decode("foo[bar]=baz&foo[list][]=a&foo[list][]=b")
// -> mapOf("foo" to mapOf("bar" to "baz", "list" to listOf("a", "b")))
// Encode
val qs: String = QS.encode(mapOf("foo" to mapOf("bar" to "baz")))
// -> "foo%5Bbar%5D=baz"
Java:
import io.github.techouse.qskotlin.QS;
// Decode
Map<@NotNull String, @Nullable Object> obj = QS.decode("foo[bar]=baz&foo[list][]=a&foo[list][]=b");
// -> {foo={bar=baz, list=[a, b]}}
// Encode
String qs = QS.encode(Map.of("foo", Map.of("bar", "baz")));
// -> "foo%5Bbar%5D=baz"
Kotlin:
// Decode
val decoded: Map<String, Any?> = QS.decode("a=c")
// => mapOf("a" to "c")
// Encode
val encoded: String = QS.encode(mapOf("a" to "c"))
// => "a=c"
Java:
// Decode
Map<@NotNull String, @Nullable Object> decoded = QS.decode("a=c");
// => {a=c}
// Encode
String encoded = QS.encode(Map.of("a", "c"));
// => "a=c"
Kotlin:
QS.decode("foo[bar]=baz")
// => mapOf("foo" to mapOf("bar" to "baz"))
QS.decode("a%5Bb%5D=c")
// => mapOf("a" to mapOf("b" to "c"))
QS.decode("foo[bar][baz]=foobarbaz")
// => mapOf("foo" to mapOf("bar" to mapOf("baz" to "foobarbaz")))
Java:
QS.decode("foo[bar]=baz");
// => {foo={bar=baz}}
QS.decode("a%5Bb%5D=c");
// => {a={b=c}}
QS.decode("foo[bar][baz]=foobarbaz");
// => {foo={bar={baz=foobarbaz}}}
Beyond the configured depth, remaining bracket content is kept as literal text:
Kotlin:
QS.decode("a[b][c][d][e][f][g][h][i]=j")
// => mapOf("a" to mapOf("b" to mapOf("c" to mapOf("d" to mapOf("e" to mapOf("f" to mapOf("[g][h][i]" to "j")))))))
Java:
QS.decode("a[b][c][d][e][f][g][h][i]=j");
// => {a={b={c={d={e={f={[g][h][i]=j}}}}}}}
Override depth:
Kotlin:
QS.decode(
"a[b][c][d][e][f][g][h][i]=j",
DecodeOptions(depth = 1)
)
// => mapOf("a" to mapOf("b" to mapOf("[c][d][e][f][g][h][i]" to "j")))
Java:
QS.decode(
"a[b][c][d][e][f][g][h][i]=j",
DecodeOptions.builder()
.depth(1)
.build()
);
// => {a={b={[c][d][e][f][g][h][i]=j}}}
Kotlin:
QS.decode(
"a=b&c=d",
DecodeOptions(parameterLimit = 1)
)
// => mapOf("a" to "b")
Java:
QS.decode(
"a=b&c=d",
DecodeOptions.builder()
.parameterLimit(1)
.build()
);
// => {a=b}
?Kotlin:
QS.decode(
"?a=b&c=d",
DecodeOptions(ignoreQueryPrefix = true)
)
// => mapOf("a" to "b", "c" to "d")
Java:
QS.decode(
"?a=b&c=d",
DecodeOptions.builder()
.ignoreQueryPrefix(true)
.build()
);
// => {a=b, c=d}
Kotlin:
QS.decode(
"a=b;c=d",
DecodeOptions(delimiter = StringDelimiter(";"))
)
// => mapOf("a" to "b", "c" to "d")
QS.decode(
"a=b;c=d",
DecodeOptions(delimiter = RegexDelimiter("[;,]"))
)
// => mapOf("a" to "b", "c" to "d")
Java:
QS.decode(
"a=b;c=d",
DecodeOptions.builder()
.delimiter(Delimiter.SEMICOLON)
.build()
);
// => {a=b, c=d}
QS.decode(
"a=b;c=d",
DecodeOptions.builder()
.delimiter(new RegexDelimiter("[;,]"))
.build()
);
// => {a=b, c=d}
Kotlin:
QS.decode(
"a.b=c",
DecodeOptions(allowDots = true)
)
// => mapOf("a" to mapOf("b" to "c"))
QS.decode(
"name%252Eobj.first=John&name%252Eobj.last=Doe",
DecodeOptions(decodeDotInKeys = true)
)
// => mapOf("name.obj" to mapOf("first" to "John", "last" to "Doe"))
Java:
QS.decode(
"a.b=c",
DecodeOptions.builder()
.allowDots(true)
.build()
);
// => {a={b=c}}
QS.decode(
"name%252Eobj.first=John&name%252Eobj.last=Doe",
DecodeOptions.builder()
.decodeDotInKeys(true)
.build()
);
// => {name.obj={first=John, last=Doe}}
Kotlin:
QS.decode(
"foo[]&bar=baz",
DecodeOptions(allowEmptyLists = true)
)
// => mapOf("foo" to emptyList<String>(), "bar" to "baz")
Java:
QS.decode(
"foo[]&bar=baz",
DecodeOptions.builder()
.allowEmptyLists(true)
.build()
);
// => {foo=[], bar=baz}
Kotlin:
QS.decode("foo=bar&foo=baz")
// => mapOf("foo" to listOf("bar", "baz"))
QS.decode(
"foo=bar&foo=baz",
DecodeOptions(duplicates = Duplicates.COMBINE)
)
// => same as above
QS.decode(
"foo=bar&foo=baz",
DecodeOptions(duplicates = Duplicates.FIRST)
)
// => mapOf("foo" to "bar")
QS.decode(
"foo=bar&foo=baz",
DecodeOptions(duplicates = Duplicates.LAST)
)
// => mapOf("foo" to "baz")
Java:
QS.decode("foo=bar&foo=baz");
// => {foo=[bar, baz]}
QS.decode(
"foo=bar&foo=baz",
DecodeOptions.builder()
.duplicates(Duplicates.COMBINE)
.build()
);
// => same as above
QS.decode(
"foo=bar&foo=baz",
DecodeOptions.builder()
.duplicates(Duplicates.FIRST)
.build()
);
// => {foo=bar}
QS.decode(
"foo=bar&foo=baz",
DecodeOptions.builder()
.duplicates(Duplicates.LAST)
.build()
);
// => {foo=baz}
Kotlin:
// latin1
QS.decode(
"a=%A7",
DecodeOptions(charset = StandardCharsets.ISO_8859_1)
)
// => mapOf("a" to "Β§")
// Sentinels
QS.decode(
"utf8=%E2%9C%93&a=%C3%B8",
DecodeOptions(
charset = StandardCharsets.ISO_8859_1,
charsetSentinel = true
)
)
// => mapOf("a" to "ΓΈ")
QS.decode(
"utf8=%26%2310003%3B&a=%F8",
DecodeOptions(
charset = StandardCharsets.UTF_8,
charsetSentinel = true
)
)
// => mapOf("a" to "ΓΈ")
Java:
QS.decode(
"a=%A7",
DecodeOptions.builder()
.charset(StandardCharsets.ISO_8859_1)
.build()
);
// => {a=Β§}
QS.decode(
"utf8=%E2%9C%93&a=%C3%B8",
DecodeOptions.builder()
.charset(StandardCharsets.ISO_8859_1)
.charsetSentinel(true)
.build()
);
// => {a=ΓΈ}
QS.decode(
"utf8=%26%2310003%3B&a=%F8",
DecodeOptions.builder()
.charset(StandardCharsets.UTF_8)
.charsetSentinel(true)
.build()
);
// => {a=ΓΈ}
Ӓ)Kotlin:
QS.decode(
"a=%26%239786%3B",
DecodeOptions(
charset = StandardCharsets.ISO_8859_1,
interpretNumericEntities = true
)
)
// => mapOf("a" to "βΊ")
Java:
QS.decode(
"a=%26%239786%3B",
DecodeOptions.builder()
.charset(StandardCharsets.ISO_8859_1)
.interpretNumericEntities(true)
.build()
);
// => {a=βΊ}
Kotlin:
QS.decode("a[]=b&a[]=c")
// => mapOf("a" to listOf("b", "c"))
QS.decode("a[1]=c&a[0]=b")
// => mapOf("a" to listOf("b", "c"))
QS.decode("a[1]=b&a[15]=c")
// => mapOf("a" to listOf("b", "c"))
QS.decode("a[]=&a[]=b")
// => mapOf("a" to listOf("", "b"))
Java:
QS.decode("a[]=b&a[]=c");
// => {a=[b, c]}
QS.decode("a[1]=c&a[0]=b");
// => {a=[b, c]}
QS.decode("a[1]=b&a[15]=c");
// => {a=[b, c]}
QS.decode("a[]=&a[]=b");
// => {a=["", b]}
Large indices convert to a map by default:
Kotlin:
QS.decode("a[100]=b")
// => mapOf("a" to mapOf(100 to "b"))
Java:
QS.decode("a[100]=b");
// => {a={100=b}}
Disable list parsing:
Kotlin:
QS.decode(
"a[]=b",
DecodeOptions(parseLists = false)
)
// => mapOf("a" to mapOf(0 to "b"))
Java:
QS.decode(
"a[]=b",
DecodeOptions.builder()
.parseLists(false)
.build()
);
// => {a={0=b}}
Mixing notations merges into a map:
Kotlin:
QS.decode("a[0]=b&a[b]=c")
// => mapOf("a" to mapOf(0 to "b", "b" to "c"))
Java:
QS.decode("a[0]=b&a[b]=c");
// => {a={0=b, b=c}}
Comma-separated values:
Kotlin:
QS.decode(
"a=b,c",
DecodeOptions(comma = true)
)
// => mapOf("a" to listOf("b", "c"))
Java:
QS.decode(
"a=b,c",
DecodeOptions.builder()
.comma(true)
.build()
);
// => {a=[b, c]}
All values decode as strings by default:
Kotlin:
QS.decode("a=15&b=true&c=null")
// => mapOf("a" to "15", "b" to "true", "c" to "null")
Java:
QS.decode("a=15&b=true&c=null");
// => {a=15, b=true, c=null}
Kotlin:
QS.encode(mapOf("a" to "b"))
// => "a=b"
QS.encode(mapOf("a" to mapOf("b" to "c")))
// => "a%5Bb%5D=c"
Java:
QS.encode(Map.of("a", "b"));
// => "a=b"
QS.encode(Map.of("a", Map.of("b", "c")));
// => "a%5Bb%5D=c"
Disable URI encoding for readability:
Kotlin:
QS.encode(
mapOf("a" to mapOf("b" to "c")),
EncodeOptions(encode = false)
)
// => "a[b]=c"
Java:
QS.encode(
Map.of("a", Map.of("b", "c")),
EncodeOptions.builder()
.encode(false)
.build()
);
// => "a[b]=c"
Values-only encoding:
Kotlin:
QS.encode(
mapOf(
"a" to "b",
"c" to listOf("d", "e=f"),
"f" to listOf(listOf("g"), listOf("h")),
),
EncodeOptions(encodeValuesOnly = true)
)
// => "a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h"
Java:
Map<String, Object> map = new LinkedHashMap<>();
map.put("a", "b");
map.put("c", List.of("d", "e=f"));
map.put("f", List.of(List.of("g"), List.of("h")));
QS.encode(
map,
EncodeOptions.builder()
.encodeValuesOnly(true)
.build()
);
// => "a=b&c[0]=d&c[1]=e%3Df&f[0][0]=g&f[1][0]=h"
Custom encoder:
Kotlin:
QS.encode(
mapOf("a" to mapOf("b" to "Δ")),
EncodeOptions(
encoder = { v, _, _ -> if (v == "Δ") "c" else v.toString() }
)
)
// => "a[b]=c" (with encode=false would be unescaped)
Java:
JValueEncoder enc = (v, cs, f) -> Objects.equals(v, "Δ") ? "c" : Objects.toString(v, "");
QS.encode(
Map.of("a", Map.of("b", "Δ")),
EncodeOptions.builder()
.encoder(enc)
.build()
);
// => "a%5Bb%5D=c"
Kotlin:
// default (indices)
QS.encode(
mapOf("a" to listOf("b", "c")),
EncodeOptions(encode = false)
)
// => "a[0]=b&a[1]=c"
// brackets
QS.encode(
mapOf("a" to listOf("b", "c")),
EncodeOptions(
encode = false,
listFormat = ListFormat.BRACKETS
)
)
// => "a[]=b&a[]=c"
// repeat
QS.encode(
mapOf("a" to listOf("b", "c")),
EncodeOptions(
encode = false,
listFormat = ListFormat.REPEAT
)
)
// => "a=b&a=c"
// comma
QS.encode(
mapOf("a" to listOf("b", "c")),
EncodeOptions(
encode = false,
listFormat = ListFormat.COMMA
)
)
// => "a=b,c"
Java:
QS.encode(
Map.of("a", List.of("b","c")),
EncodeOptions.builder()
.encode(false)
.listFormat(ListFormat.INDICES)
.build()
);
// => "a[0]=b&a[1]=c"
QS.encode(
Map.of("a", List.of("b","c")),
EncodeOptions.builder()
.encode(false)
.listFormat(ListFormat.BRACKETS)
.build()
);
// => "a[]=b&a[]=c"
QS.encode(
Map.of("a", List.of("b","c")),
EncodeOptions.builder()
.encode(false)
.listFormat(ListFormat.REPEAT)
.build()
);
// => "a=b&a=c"
QS.encode(
Map.of("a", List.of("b","c")),
EncodeOptions.builder()
.encode(false)
.listFormat(ListFormat.COMMA)
.build()
);
// => "a=b,c"
Note: When ListFormat.COMMA is selected, you can also set EncodeOptions.commaRoundTrip to
true or false to append [] on single-element lists so they round-trip through decoding. Set
EncodeOptions.commaCompactNulls to true alongside the comma format when you'd like to drop
null entries instead of preserving empty slots (for example, listOf("one", null, "two")
becomes one,two).
Kotlin:
QS.encode(
mapOf("a" to mapOf("b" to mapOf("c" to "d", "e" to "f"))),
EncodeOptions(encode = false)
)
// => "a[b][c]=d&a[b][e]=f"
Java:
Map<String, Object> inner = new LinkedHashMap<>();
inner.put("c","d"); inner.put("e","f");
Map<String, Object> mid = new LinkedHashMap<>(); mid.put("b", inner);
Map<String, Object> root = new LinkedHashMap<>(); root.put("a", mid);
QS.encode(
root,
EncodeOptions.builder()
.encode(false)
.build()
);
// => "a[b][c]=d&a[b][e]=f"
Dot notation:
Kotlin:
QS.encode(
mapOf("a" to mapOf("b" to mapOf("c" to "d", "e" to "f"))),
EncodeOptions(
encode = false,
allowDots = true
)
)
// => "a.b.c=d&a.b.e=f"
Java:
QS.encode(
root,
EncodeOptions.builder()
.allowDots(true)
.encode(false)
.build()
);
// => "a.b.c=d&a.b.e=f"
Encode dots in keys:
Kotlin:
QS.encode(
mapOf("name.obj" to mapOf("first" to "John", "last" to "Doe")),
EncodeOptions(
allowDots = true,
encodeDotInKeys = true
)
)
// => "name%252Eobj.first=John&name%252Eobj.last=Doe"
Java:
QS.encode(
Map.of("name.obj", Map.of("first","John","last","Doe")),
EncodeOptions.builder()
.allowDots(true)
.encodeDotInKeys(true)
.build()
);
// => "name%252Eobj.first=John&name%252Eobj.last=Doe"
Allow empty lists:
Kotlin:
QS.encode(
mapOf("foo" to emptyList<String>(), "bar" to "baz"),
EncodeOptions(
encode = false,
allowEmptyLists = true
)
)
// => "foo[]&bar=baz"
Java:
Map<String, Object> emptyMap = new LinkedHashMap<>();
emptyMap.put("foo", List.of()); emptyMap.put("bar", "baz");
QS.encode(
emptyMap,
EncodeOptions.builder()
.allowEmptyLists(true)
.encode(false)
.build()
);
// => "foo[]&bar=baz"
Empty strings & nulls:
Kotlin:
QS.encode(mapOf("a" to ""))
// => "a="
Java:
QS.encode(Map.of("a", ""));
// => "a="
Return empty string for empty containers:
Kotlin:
QS.encode(mapOf("a" to emptyList<String>())) // => ""
QS.encode(mapOf("a" to emptyMap<String, Any>())) // => ""
QS.encode(mapOf("a" to listOf(emptyMap<String, Any>()))) // => ""
QS.encode(mapOf("a" to mapOf("b" to emptyList<String>()))) // => ""
QS.encode(mapOf("a" to mapOf("b" to emptyMap<String, Any>()))) // => ""
Java:
QS.encode(Map.of("a", List.of())); // => ""
QS.encode(Map.of("a", Map.of())); // => ""
QS.encode(Map.of("a", List.of(Map.of()))); // => ""
QS.encode(Map.of("a", Map.of("b", List.of()))); // => ""
QS.encode(Map.of("a", Map.of("b", Map.of()))); // => ""
Omit Undefined:
Kotlin:
QS.encode(mapOf("a" to null, "b" to Undefined()))
// => "a="
Java:
Map<String, Object> omit = new LinkedHashMap<>();
omit.put("a", null); omit.put("b", Undefined.INSTANCE);
QS.encode(omit);
// => "a="
Add query prefix:
Kotlin:
QS.encode(
mapOf("a" to "b", "c" to "d"),
EncodeOptions(addQueryPrefix = true)
)
// => "?a=b&c=d"
Java:
QS.encode(
Map.of("a","b","c","d"),
EncodeOptions.builder()
.addQueryPrefix(true)
.build()
);
// => "?a=b&c=d"
Custom delimiter:
Kotlin:
QS.encode(
mapOf("a" to "b", "c" to "d"),
EncodeOptions(delimiter = Delimiter.SEMICOLON)
)
// => "a=b;c=d"
Java:
QS.encode(
Map.of("a","b","c","d"),
EncodeOptions.builder()
.delimiter(Delimiter.SEMICOLON)
.build()
);
// => "a=b;c=d"
By default, LocalDateTime is serialized using toString().
Kotlin:
val date = java.time.LocalDateTime.ofInstant(java.time.Instant.ofEpochMilli(7), java.time.ZoneId.systemDefault())
QS.encode(mapOf("a" to date), EncodeOptions(encode = false))
// => "a=1970-01-01T01:00:00.007" (example output depends on system zone)
QS.encode(
mapOf("a" to date),
EncodeOptions(
encode = false,
dateSerializer = { d -> d.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli().toString() }
)
)
// => "a=7"
Java:
var date = java.time.LocalDateTime.ofInstant(java.time.Instant.ofEpochMilli(7), java.time.ZoneId.systemDefault());
QS.encode(
Map.of("a", date),
EncodeOptions.builder()
.encode(false)
.build()
);
// => "a=1970-01-01T01:00:00.007" (example output depends on system zone)
QS.encode(
Map.of("a", date),
EncodeOptions.builder()
.encode(false)
.dateSerializer(d -> Long.toString(d.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli()))
.build()
);
// => "a=7"
Kotlin:
// Sort keys
QS.encode(
mapOf("a" to "c", "z" to "y", "b" to "f"),
EncodeOptions(
encode = false,
sort = { a, b -> a.toString().compareTo(b.toString()) }
)
)
// => "a=c&b=f&z=y"
// Filter by function (drop/transform values)
QS.encode(
mapOf("a" to "b", "c" to "d", "e" to mapOf("f" to java.time.Instant.ofEpochMilli(123), "g" to listOf(2))),
EncodeOptions(
encode = false,
filter = FunctionFilter { prefix, value ->
when (prefix) {
"b" -> Undefined()
"e[f]" -> (value as java.time.Instant).toEpochMilli()
"e[g][0]" -> (value as Number).toInt() * 2
else -> value
}
}
)
)
// => "a=b&c=d&e[f]=123&e[g][0]=4"
// Filter by explicit list of keys/indices
QS.encode(
mapOf("a" to "b", "c" to "d", "e" to "f"),
EncodeOptions(
encode = false,
filter = IterableFilter(listOf("a", "e"))
)
)
// => "a=b&e=f"
QS.encode(
mapOf("a" to listOf("b", "c", "d"), "e" to "f"),
EncodeOptions(
encode = false,
filter = IterableFilter(listOf("a", 0, 2))
)
)
// => "a[0]=b&a[2]=d"
Java:
// Sort keys
QS.encode(
Map.of("a","c","z","y","b","f"),
EncodeOptions.builder()
.encode(false)
.sort(Comparator.comparing(o -> o.toString()))
.build()
);
// => "a=c&b=f&z=y"
// Function filter
Map<String, Object> input = new LinkedHashMap<>();
input.put("a","b"); input.put("c","d");
Map<String, Object> eMap = new LinkedHashMap<>();
eMap.put("f", java.time.Instant.ofEpochMilli(123));
eMap.put("g", List.of(2));
input.put("e", eMap);
FunctionFilter fn = FunctionFilter.from((k,v) -> switch(k) {
case "b" -> Undefined.INSTANCE;
case "e[f]" -> ((java.time.Instant)v).toEpochMilli();
case "e[g][0]" -> ((Number)v).intValue()*2;
default -> v;
});
QS.encode(
input,
EncodeOptions.builder()
.encode(false)
.filter(fn)
.build()
);
// => "a=b&c=d&e[f]=123&e[g][0]=4"
// Iterable filters
QS.encode(
Map.of("a","b","c","d","e","f"),
EncodeOptions.builder()
.encode(false)
.filter(new IterableFilter(List.of("a","e")))
.build()
);
// => "a=b&e=f"
QS.encode(
Map.of("a", List.of("b","c","d"), "e","f"),
EncodeOptions.builder()
.encode(false)
.filter(new IterableFilter(List.of("a",0,2)))
.build()
);
// => "a[0]=b&a[2]=d"
Kotlin:
QS.encode(mapOf("a" to "b c"))
// => "a=b%20c" (RFC 3986 default)
QS.encode(
mapOf("a" to "b c"),
EncodeOptions(format = Format.RFC3986)
)
// => "a=b%20c"
QS.encode(
mapOf("a" to "b c"),
EncodeOptions(format = Format.RFC1738)
)
// => "a=b+c"
Java:
QS.encode(Map.of("a","b c"));
// => "a=b%20c" (RFC 3986 default)
QS.encode(
Map.of("a","b c"),
EncodeOptions.builder()
.format(Format.RFC3986)
.build()
);
// => "a=b%20c"
QS.encode(
Map.of("a","b c"),
EncodeOptions.builder()
.format(Format.RFC1738)
.build()
);
// => "a=b+c"
| Port | Repository | Package |
|---|---|---|
| Dart | techouse/qs | |
| Python | techouse/qscodec | |
| Swift / Objective-C | techouse/qs-swift | |
| .NET / C# | techouse/qs-net | |
| Node.js (original) | ljharb/qs |
BSD 3-Clause Β© techouse