The marshaller allows you to convert a form-urlencoded
string to a POJO/Map object and vice versa. Supports nesting of objects, lists, arrays. Supports indexed and non-indexed lists.
<dependency>
<groupId>org.touchbit.web</groupId>
<artifactId>form-urlencoded-marshaller</artifactId>
<version>1.0.0</version>
</dependency>
org.touchbit.web:form-urlencoded-marshaller:jar:1.0.0
+- org.apache.commons:commons-lang3:jar:3.12.0:compile
- Marshal POJO/
Map<String, Object>
to URL form data string. - Unmarshal URL form data string to POJO/
Map<String, Object>
. - Support for nested POJO/Map
foo[bar]=100
<->{foo={bar=100}}
. - Support for hidden arrays
foo=100&foo=200...&foo=100500
. - Support for implicit arrays
foo[]=100&foo[]=200...&foo[]=100500
. - Support for explicit arrays
foo[0]=100&foo[1]=200...&foo[n]=100500
. - Converting string values to POJO field types.
- Rules for handling null values (ignore, null string, empty string, null marker).
- AdditionalProperties field for extra form data parameters (like Jackson2).
FormUrlMarshaller is not a utility class. You can inherit from it and change the current implementation of internal methods to suit your needs. All internal methods have the protected
modifier. There are no finalized methods or classes.
Marshaling can be done in three ways:
String marshal(Object)
- converts a POJO or Map to a string inform URL encoded
format.Map<String, List<String>> marshalToMap(Object)
- converts a POJO or Map to aform URL encoded
Map where key - URL form Key, value - list of encoded values. For example:{foo=[1, 2], bar=car} <--> {foo=[1, 2], bar=[car]}
. Allows you to implement your own processing ofform URL encoded
lists.IChain marshalToIChain(Object)
- converts a POJO or Map toIChain
object.IChain
- this is a chain of encoded url form parameters. Allows you to implement your own processing ofform URL encoded
string data.
Unmarshaling can be done in two ways:
<M> void unmarshalTo(M, String)
- writeform URL encoded
data to a POJO or Map object.<M> M unmarshal(Class<M>, String)
- writeform URL encoded
data to a POJO or Map (independently creates class instances).
(D) - default
FormUrlMarshaller.INSTANCE
.enableHiddenList() - foo=100&foo=200 (D)
.enableImplicitList() - foo[]=100&foo[]=200
.enableExplicitList() - foo[0]=100&foo[1]=200
.setNullValueRule(RULE_IGNORE) - Ignore null value parameters (D)
.setNullValueRule(RULE_NULL_MARKER) - /api/call?foo=%00
.setNullValueRule(RULE_EMPTY_STRING) - /api/call?foo=
.setNullValueRule(RULE_NULL_STRING) - /api/call?foo=null
.prohibitAdditionalProperties(true) - error if extra fields received (D false)
.setFormUrlCodingCharset(UTF_16); - value encoding (D utf-8)
@lombok.Data
@FormUrlEncoded
public class Pagination {
@FormUrlEncodedField("limit")
private Integer limit;
@FormUrlEncodedField("offset")
private Integer offset;
}
Usage
public class Example {
public static void main(String[] args) {
final FormUrlMarshaller marshaller = FormUrlMarshaller.INSTANCE;
final Pagination pagination = new Pagination().limit(50).offset(10);
final String form = marshaller.marshal(pagination);
System.out.println("UrlEncoded form: " + form);
final Pagination pojo = marshaller.unmarshal(Pagination.class, form);
System.out.println("Pagination POJO: " + pojo);
}
}
Output
UrlEncoded form: offset=10&limit=50
Pagination POJO: {offset=10, limit=50}
@lombok.Data
@FormUrlEncoded
public static class QueryParam {
@FormUrlEncodedField("sorting")
private String sorting;
@FormUrlEncodedField("exclude")
private String exclude;
// POJO from the previous example
@FormUrlEncodedField("paginate")
private Pagination paginate;
}
Usage
public class Example {
public static void main(String[] args) {
FormUrlMarshaller marshaller = FormUrlMarshaller.INSTANCE;
Pagination paginate = new Pagination().limit(50).offset(10);
QueryParam query = new QueryParam()
.exclude("<,>").sorting("DESC").paginate(paginate);
final String form = marshaller.marshal(query);
System.out.println("UrlEncoded form: " + form);
final QueryParam pojo = marshaller.unmarshal(QueryParam.class, form);
System.out.println("QueryParam POJO: " + pojo);
}
}
Output
UrlEncoded form: paginate[offset]=10&paginate[limit]=50&sorting=DESC&exclude=%3C%2C%3E
QueryParam POJO: {exclude=<,>, sorting=DESC, paginate={offset=10, limit=50}}
Used during unmarshalling to store extra fields that are not present in the POJO model.
AP field must have the @FormUrlEncodedAdditionalProperties
annotation.
AP field type - strictly Map<String, Object>
.
@lombok.Data
@FormUrlEncoded
public static class FormData {
@FormUrlEncodedField("firstName")
private String firstName;
@FormUrlEncodedField("lastName")
private String lastName;
@FormUrlEncodedAdditionalProperties()
public Map<String, Object> additionalProperties;
}
Usage
public class Example {
public static void main(String[] args) {
FormUrlMarshaller marshaller = FormUrlMarshaller.INSTANCE;
final String data =
"lastName=Pearson&firstName=Michael&nickname=Gentlemen";
final FormData unmarshal = marshaller.unmarshal(FormData.class, data);
System.out.println("QueryParam POJO: " + unmarshal);
}
}
Output
UrlEncoded form: lastName=Pearson&firstName=Michael&nickname=Gentlemen
QueryParam POJO: {firstName=Michael, lastName=Pearson, additionalProperties={nickname=Gentlemen}}
Prohibition of extra fields
public class Example {
public static void main(String[] args) {
FormUrlMarshaller marshaller = FormUrlMarshaller.INSTANCE
.prohibitAdditionalProperties(true); // <-------- PROHIBIT
final String data = "lastName=Pearson&" +
"firstName=Michael&" +
"nickname=Gentlemen";
final FormData form = marshaller.unmarshal(FormData.class, data);
System.out.println("QueryParam POJO: " + form);
}
}
MarshallerException:
URL encoded string contains unmapped additional properties.
Actual: {nickname=Gentlemen}
Expected: There are no additional properties.
Marshalling and unmarshaling methods only throw MarshallerException
(RuntimeException
).
Errors during the operation of the marshaller are as detailed as possible. For example, if the FormUrlEncoded string contains an array of objects instead of a single object.
org.touchbit.www.form.urlencoded.marshaller.util.MarshallerException:
Incompatible types received for conversion.
Source: {pagination=[{limit=50, offset=10}]}
Source field: pagination
Source value: [{limit=50, offset=10}]
Source type: java.util.ArrayList
Target type: qa.model.QueryParams
Target field: private Pagination pagination;
JMH version: 1.34
VM version: JDK 15.0.4, Zulu OpenJDK 64-Bit Server VM, 15.0.4+5-MTS
Blackhole mode: full + dont-inline hint
Warmup: 5 iterations, 10 s each
Measurement: 5 iterations, 10 s each
Threads: 4 threads, will synchronize iterations
Benchmark mode: Average time, time/op
- Each benchmark contains a pre-prepared set of data for marshaling and unmarshaling.
- Marshalling and unmarshaling is checked on data of
[16,32,64...1024]
bytes length. - As the data size increases, the number of involved object fields increases. An example for clarity:
- 16 byte ->
offset=0&limit=5
- 32 byte ->
offset=2&limit=50&sort=ASC&foo=1
- 16 byte ->
- GC is performed between measurements.
- The results of marshaling and unbarshaling are dumped into the
Blackhole
. - Used 6 load profiles:
- FormUrlEncoded String <-->
Map<String, String>
- FormUrlEncoded String <--> POJO with fields of type
String
- FormUrlEncoded String <--> POJO with fields of type
Integer
- FormUrlEncoded String <--> POJO with fields of type
LIst<String>
- FormUrlEncoded String <--> POJO with fields of type
LIst<String>
- FormUrlEncoded String <--> POJO with nested POJOs fields
- FormUrlEncoded String <-->
Click image for detailed JMH report
Copyright (c) 2022 Shaburov Oleg.
Distributed under license 'Apache License Version 2.0'.
See the LICENSE file for license rights and limitations.