Skip to main content

Serializers and Parsers

One of the goals of Juneau was to make serialization as simple as possible. In a single line of code, you should be able to serialize and parse most POJOs.

Despite this simplicity, Juneau provides lots of extensibility and configuration properties for tailoring how POJOs are serialized and parsed.

The built-in serializers in Juneau are fast, efficient, and highly configurable. They work by serializing POJOs directly to streams instead of using intermediate Document Object Model objects.

In most cases, you can serialize objects in one line of code by using one of the default serializers:

// A simple bean
public class Person {
public String name = "John Smith";
public int age = 21;
}

// Serialize to JSON, XML, or HTML
Person person = new Person();

// Produces:
// "{\"name\":\"John Smith\",\"age\":21}"
String json = JsonSerializer.DEFAULT.write(person);

// Produces:
// "{name:'John Smith',age:21}"
String json = Json5Serializer.DEFAULT.write(person);

// Produces:
// John Smith
// 21
String xml = XmlSerializer.DEFAULT.write(person);

// Produces:
// keyvalue
// nameJohn Smith
// age21
//
String html = HtmlSerializer.DEFAULT.write(person);

// Produces:
// "(name='John Smith',age=21)"
String uon = UonSerializer.DEFAULT.write(person);

// Produces:
// "name='John+Smith'&age=21"
String urlencoding = UrlEncodingSerializer.DEFAULT.write(person);

// Produces:
// 82 A4 6E 61 6D 65 AA 4A 6F 68 6E 20 53 6D 69 74 68 A3 61 67 65 15
byte[] bytes = MsgPackSerializer.DEFAULT.write(person);

In addition to the default serializers, customized serializers can be created using various built-in options:

// Use one of the default serializers to serialize a POJO
String json = JsonSerializer.DEFAULT.write(someObject);

// Create a custom serializer for lax syntax using single quote characters
JsonSerializer serializer = Json5Serializer.create().build();

// Clone an existing serializer and modify it to use single-quotes
JsonSerializer serializer = JsonSerializer.DEFAULT.copy().sq().build();

// Serialize a POJO to JSON
String json = serializer.write(someObject);

Default serialization support is provided for Java primitives, Maps, Collections, beans, arrays, date/time types (Calendar, Date, Temporal subtypes, XMLGregorianCalendar, Duration), and streamable sequence types (Iterator, Iterable, Stream, Enumeration). Extensible support for other data types is available through the use of POJO swaps (described later).

Parsers work by parsing input directly into POJOs instead of having to create intermediate Document Object Models. This allows them to parse input with minimal object creation.

Like the serializers, you can often parse objects in one line of code by using one of the default parsers:

// Use one of the predefined parsers.
Parser parser = JsonParser.DEFAULT;

// Parse a JSON object as a bean.
String json = "{name:'John Smith',age:21}";
Person person = parser.read(json, Person.class);

// Or parse it into a generic Map.
Map<String,Object> map = parser.read(json, Map.class);

// Parse a JSON string.
json = "'foobar'";
String string = parser.read(json, String.class);

// Parse a JSON number as a Long or Float.
json = "123";
Long _long = parser.read(json, Long.class);
Float _float = parser.read(json, Float.class);

// Parse a JSON object as a HashMap.
json = "{a:{name:'John Smith',age:21},b:{name:'Joe Smith',age:42}}";
Map<String,Person> map2 = parser.read(json, HashMap.class, String.class, Person.class);

// Parse a JSON object as a HashMap<String,List<Person>>.
json = "{a:[{name:'John Smith',age:21},{name:'Joe Smith',age:42}]}";
Map<String,List<Person>> map3 = parser.read(json, HashMap.class, String.class,
LinkedList.class, Person.class);

// Parse a JSON array of integers as a Collection of Integers or int[] array.
json = "[1,2,3]";
List<Integer> list = parser.read(json, LinkedList.class, Integer.class);
int[] ints = parser.read(json, int[].class);

The parsers can also be used to populating existing bean and collection objects:

// Use one of the predefined parsers.
Parser parser = JsonParser.DEFAULT;

// Populate the properties on an existing bean from a JSON object.
String json = "{name:'John Smith',age:21}";
Person person = new Person();
parser.parseIntoBean(json, person);

// Populate an existing list from a JSON array of numbers.
json = "[1,2,3]";
List<Integer> list = new LinkedList<>();
parser.readIntoCollection(json, list, Integer.class);

// Populate an existing map from a JSON object containing beans.
json = "{a:{name:'John Smith',age:21},b:{name:'Joe Smith',age:42}}";
Map<String,Person> map = new TreeMap<>();
parser.readIntoMap(json, map, String.class, Person.class);
note

In the example above, we're parsing JSON5 (single quotes, unquoted attributes). The JSON parser can handle any valid JSON syntax (such as quoted or unquoted attributes, single or double quotes). It can also handle JSON fragments and embedded Javascript comments.

Many of the JSON examples provided will use JSON5 syntax which is easier to read since we don't have to deal with escapes.

See Also
ImageSerializer - Example of a custom serializer.ImageParser - Example of a custom parser.

Builder ergonomics​

As of 10.0, subclasses of Serializer and Parser builders no longer need to override every fluent setter to preserve covariant return types. The CRTP self-type (SELF extends SerializerBuilder<SELF>) handles covariant returns automatically — bespoke setters on a builder subclass only need to call return self() and the correct concrete type flows through the whole chain without any boilerplate overrides.

Per-Session Property Overrides​

Serializer and parser contexts are immutable once built. However, individual sessions (created via createSession()) support per-request configuration overrides without rebuilding the context.

Use Session.Builder.property(String key, Object value) or properties(Map<String,Object>) to override specific settings for a single serialization or parse operation:

// Override settings for a single request without rebuilding the serializer
String json = JsonSerializer.DEFAULT
.createSession()
.property("escapeSolidus", true)
.property("addBeanTypes", true)
.property("maxIndent", 2)
.build()
.write(myBean);

// Override parser settings per-request
MyBean bean = JsonParser.DEFAULT
.createSession()
.property("trimStrings", true)
.property("validateEnd", true)
.build()
.read(json, MyBean.class);

Both short property names (e.g. "escapeSolidus") and fully-qualified names (e.g. "JsonSerializerSession.escapeSolidus") are accepted. String values are automatically coerced to the target type using BasicConverter, so you can pass "true" where a boolean is expected, or "fr-FR" where a Locale is expected.

// All of these are equivalent
session.property("escapeSolidus", true);
session.property("escapeSolidus", "true");
session.property("JsonSerializerSession.escapeSolidus", true);

The full catalog of per-session properties — the base, serializer, parser, and format-specific (JSON/JSON5, UON, URL-Encoding, XML, HTML, CSV) property tables — lives on the Context Settings page under Per-Session Property Reference. That page is grounded in the MarshallingSession hierarchy and its serializer/parser session subclasses.

REST Integration​

Per-session properties are particularly useful in REST contexts where per-request control is needed. Use RestRequest to set serializer and parser session properties for the current request:

@RestGet
public MyBean get(RestRequest req) {
// Configure serializer for this specific request
req.setSerializerSessionProperty("addBeanTypes", true);
req.setSerializerSessionProperty("maxIndent", 2);
return myBean;
}

@RestPut
public void put(RestRequest req, @Body MyBean body) {
// Configure parser for this specific request
req.setParserSessionProperty("trimStrings", true);
// body is then parsed with trimStrings=true
}

HTTP-Based Session Options​

Clients can also pass session properties via HTTP headers or query parameters when the server has enabled them. The server controls which properties are allowed using @Rest(allowedSerializerOptions, allowedParserOptions) or @RestOp(allowedSerializerOptions, allowedParserOptions):

// Allow clients to override these specific serializer properties
@Rest(
allowedSerializerOptions={"useWhitespace", "addBeanTypes"},
allowedParserOptions={"trimStrings"}
)
public class MyResource extends BasicRestServlet {
...
}

Clients send options using the X-Juneau-Serializer-Options header (JSON5 format) or juneauSerializerOptions query parameter (UON format). The serializerSessionOptionsHeader(...) and serializerSessionOptionsQuery(...) convenience methods live on the classic REST client stack (RestClient (classic) / org.apache.juneau.rest.client.classic) only — the default next-generation RestClient does not have them:

// Client using Juneau REST client (classic)
RestClient client = RestClient.create()
.rootUrl("http://localhost:8080/myresource")
.build();

// Via header (JSON5 format)
client.get("/mybean")
.serializerSessionOptionsHeader("{useWhitespace:true,addBeanTypes:false}")
.run()
.getContent().as(MyBean.class);

// Via query parameter (UON format)
client.get("/mybean")
.serializerSessionOptionsQuery("(useWhitespace=true)")
.run()
.getContent().as(MyBean.class);

See Session Options via HTTP for complete documentation.