Skip to content

The marshaller allows you to convert a FormUrlEncoded string to a POJO/Map object and vice versa. Supports nesting of objects, lists, arrays. Supports indexed and non-indexed lists.

License

Notifications You must be signed in to change notification settings

touchbit/form-urlencoded-marshaller

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Marshaller for "x-www-form-urlencoded" data

Build Quality Gate Status

Security Rating Reliability Rating Maintainability Rating Coverage Lines of Code


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

TOC


Features

  • 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).

Back to top

General information

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 in form URL encoded format.
  • Map<String, List<String>> marshalToMap(Object) - converts a POJO or Map to a form 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 of form URL encoded lists.
  • IChain marshalToIChain(Object) - converts a POJO or Map to IChain object. IChain - this is a chain of encoded url form parameters. Allows you to implement your own processing of form URL encoded string data.

Unmarshaling can be done in two ways:

  • <M> void unmarshalTo(M, String) - write form URL encoded data to a POJO or Map object.
  • <M> M unmarshal(Class<M>, String) - write form URL encoded data to a POJO or Map (independently creates class instances).

Back to top

Settings

(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)

Back to top

Usage

Simple POJO (flat data)

@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}

Back to top

Complex POJO (nested objects)

@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}}

Back to top

Additional properties

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.

Back to top

Error handling

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;

Back to top

Benchmarks

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
  • 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

Brief results

Back to top

Detailed results

Click image for detailed JMH report

Back to top

Licensing

Copyright (c) 2022 Shaburov Oleg.
Distributed under license 'Apache License Version 2.0'.
See the LICENSE file for license rights and limitations.

About

The marshaller allows you to convert a FormUrlEncoded string to a POJO/Map object and vice versa. Supports nesting of objects, lists, arrays. Supports indexed and non-indexed lists.

Topics

Resources

License

Stars

Watchers

Forks