Skip navigation links

Package org.apache.juneau.transform

Transform API

See: Description

Package org.apache.juneau.transform Description

Transform API

Table of Contents
  1. Transforms

    1. BeanFilter Class

    2. PojoSwap Class

    3. One-Way PojoSwaps

    4. Stop Classes

    5. Surrogate Classes

    6. Serializing to ObjectMaps

1 - Transforms

By default, the Juneau framework can serialize and parse a wide variety of POJOs out-of-the-box. However, two special classes are provided tailor how certain Java objects are handled by the framework. These classes are:

  • BeanFilter - Transforms that alter the way beans are handled.
  • PojoSwap - Transforms that swap non-serializable POJOs with serializable POJOs during serialization (and optionally vis-versa during parsing).
    1. StringSwap - Convenience subclass for swaps that convert objects to strings.
    2. MapSwap - Convenience subclass for swaps that convert objects to maps.

Transforms are added to serializers and parsers in a variety of ways:

Swaps can also be associated with classes through the @Swap annotation.

1.1 - BeanFilter Class

Bean filters are used to tailor how Juneau handles bean classes. They can be used for the following purposes:

  • Include or exclude which properties are exposed in beans, or the order those properties are serialized.
  • Define property-namers for customizing bean property names.
  • Define bean subtypes.
  • Define bean interface classes.

It should be noted that the @Bean annotation provides equivalent functionality through annotations. However, the BeanFilter class allows you to provide the same features when you do not have access to the source code.

Explicitly specify which properties are visible on a bean class

// Define bean filter that orders properties by "age" then "name" public class MyBeanFilter extends BeanFilter<Person> { public MyBeanFilter() { setProperties("age","name"); } } WriterSerializer s = JsonSerializer.create().beanFilters(MyBeanFilter.class).build(); Person p = getPerson(); String json = s.serialize(p); // Prints "{age:45,name:'John Smith'}"

Note that this is equivalent to specifying the following annotation on the bean class:

@Bean(properties="age,name") public class Person { ... }

Exclude which properties are visible on a bean class

// Define bean filter that excludes "name" public class MyBeanFilter extends BeanFilter<Person> { public MyBeanFilter() { setExcludeProperties("name"); } } WriterSerializer s = JsonSerializer.create().beanFilters(MyBeanFilter.class).build(); Person p = getPerson(); String json = s.serialize(p); // Prints "{age:45}"

Note that this is equivalent to specifying the following annotation on the bean class:

@Bean(excludeProperties={"name"}) public class Person { ... }

Define specialized property namers

// Define bean filter with our own property namer. public class MyBeanFilter extends BeanFilter<Person> { public MyBeanFilter() { setPropertyNamer(UpperCasePropertyNamer.class); } } // Define property namer that upper-cases the property names public class UpperCasePropertyNamer implements PropertyNamer { @Override public String getPropertyName(String name) { return name.toUpperCase(); } } // Serialize to JSON WriterSerializer s = JsonSerializer.create().beanFilters(MyBeanFilter.class).build(); Person person = getPerson(); String json = s.serialize(p); // Prints "{AGE:45,NAME:'John Smith'}" // Parse back into bean ReaderParser p = JsonParser.create().beanFilters(MyBeanFilter.class).build(); person = p.parse(json, Person.class); // Read back into original object

Note that this is equivalent to specifying the following annotation on the bean class:

@Bean(propertyNamer=UpperCasePropertyNamer.class) public class Person { ... }

Limiting bean properties to parent bean classes

Occasionally, you may want to limit bean properties to some parent interface. For example, in the RequestEchoResource class in the sample war file, we serialize instances of HttpServletRequest and HttpServletResponse. However, we really only want to serialize the properties defined on those specific APIs, not vendor-specific methods on the instances of those classes. This can be done through the interfaceClass property of a bean filter.

For example, let's define the following parent class and subclass:

// Abstract parent class public abstract class MyClass { public String foo="foo"; } // Subclass 1 public class MyClassBar extends MyClass { public String bar="bar"; }

Suppose we only want to render the properties defined on MyClass, not those defined on child classes. To do so, we can define the following bean filter:

// Define bean filter that limits properties to only those defined on MyClass public class MyBeanFilter extends BeanFilter<MyClass> { public MyBeanFilter() { setInterfaceClass(MyClass.class); } }

When serialized, the serialized bean will only include properties defined on the parent class.

// Serialize to JSON WriterSerializer s = JsonSerializer.create().beanFilters(MyBeanFilter.class).build(); MyClass c = new MyClassBar(); String json = s.serialize(p); // Prints "{foo:'foo'}"

The equivalent can be done through an annotation on the parent class, which applies to all child classes:

@Bean(interfaceClass=MyClass.class) public abstract class MyClass { public String foo="foo"; }

The annotation can also be applied on the individual child classes, like so...

@Bean(interfaceClass=MyClass.class) public class MyClassBar extends MyClass { public String bar="bar"; }

Also, the *BeanFilters(...) methods will automatically interpret any non-BeanFilter classes passed in as meaning interface classes. So in the previous example, the BeanFilter class could have been avoided altogether by just passing in MyClass.class to the serializer, like so:

// Serialize to JSON WriterSerializer s = JsonSerializer.create().beanFilters(MyClass.class).build();

In fact, this is the shortcut used in the RequestEchoResource sample class:

@RestResource( beanFilters={ // Interpret these as their parent classes, not subclasses HttpServletRequest.class, HttpSession.class, ServletContext.class } ) public class RequestEchoResource extends RestServletDefault {

Allowing non-public bean classes/methods/fields to be used by the framework

By default, only public classes are interpreted as beans. Non-public classes are treated as 'other' POJOs that are typically just serialized to strings using the toString() method. Likewise, by default, only public fields/methods are interpreted as bean properties.

The following bean context properties can be used to allow non-public classes/methods/fields to be used by the framework:

Also, specifying a @BeanProperty annotation on non-public getters/setters/fields will also allow them to be detected by the framework.

public class MyBean { // A bean property public int f1; // Not a bean property @BeanIgnore public int f2; // A bean property @BeanProperty protected int f3; // A bean property @BeanProperty private int getF3() {...} }

1.2 - PojoSwap Class

PojoSwaps are a critical component of the Juneau architecture. They allow the Juneau serializers and parsers to be extended to handle virtually any kind of Java object.

As explained in the overview, Juneau has built-in support for serializing and parsing specific kinds of objects, like primitive objects, bean, maps, collections, and arrays. Other kinds of POJOs, such as Date objects, cannot be serialized properly, since they are not true beans. This is where PojoSwaps come into play.

The purpose of an PojoSwap is to convert a non-serializable object to a serializable surrogate form during serialization, and to optionally convert that surrogate form back into the original object during parsing.

For example, the following swap can be used to convert Date objects to ISO8601 strings during serialization, and Date objects from ISO8601 string during parsing:

// Sample swap for converting Dates to ISO8601 strings. public class MyDateSwap extends StringSwap<Date> { // ISO8601 formatter. private DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); /** Converts a Date object to an ISO8601 string. */ @Override public String swap(BeanSession session, Date o) { return format.format(o); } /** Converts an ISO8601 string to a Date object. */ @Override public Date unswap(BeanSession session, String o, ClassMeta hint) throws ParseException { try { return format.parse(o); } catch (java.text.ParseException e) { throw new ParseException(e); } } }

The swap above can then be associated with serializers and parsers as the following example shows:

// Sample bean with a Date field. public class MyBean { public Date date = new Date(112, 2, 3, 4, 5, 6); } // Create a new JSON serializer, associate our date swap with it, and serialize a sample bean. Serializer serializer = JsonSerializer.create().pojoSwaps(MyDateSwap.class).build(); String json = serializer.serialize(new MyBean()); // == "{date:'2012-03-03T04:05:06-0500'}" // Create a JSON parser, associate our date swap with it, and reconstruct our bean (including the date). ReaderParser parser = JsonParser.create().pojoSwaps(MyDateSwap.class).build(); MyBean bean = parser.parse(json, MyBean.class); int day = bean.date.getDay(); // == 3

In addition, the BeanMap.get(Object) and BeanMap.put(String,Object) methods will automatically convert to swapped values as the following example shows:

// Create a new bean context and add our swap. BeanContext beanContext = new BeanContext().pojoSwaps(MyDateSwap.class); // Create a new bean. MyBean myBean = new MyBean(); // Wrap it in a bean map. BeanMap<Bean> beanMap = beanContext.forBean(myBean); // Use the get() method to get the date field as an ISO8601 string. String date = (String)beanMap.get("date"); // == "2012-03-03T04:05:06-0500" // Use the put() method to set the date field to an ISO8601 string. beanMap.put("date", "2013-01-01T12:30:00-0500"); // Set it to a new value. // Verify that the date changed on the original bean. int year = myBean.date.getYear(); // == 113

Another example of a PojoSwap is one that converts byte[] arrays to BASE64-encoded strings:

public class ByteArrayBase64Swap extends StringSwap<byte[]> { @Override public String swap(byte[] b) throws SerializeException { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStream b64os = MimeUtility.encode(baos, "base64"); b64os.write(b); b64os.close(); return new String(baos.toByteArray()); } catch (Exception e) { throw new SerializeException(e); } } @Override public byte[] unswap(String s, ClassMeta<?> hint) throws ParseException { try { byte[] b = s.getBytes(); ByteArrayInputStream bais = new ByteArrayInputStream(b); InputStream b64is = MimeUtility.decode(bais, "base64"); byte[] tmp = new byte[b.length]; int n = b64is.read(tmp); byte[] res = new byte[n]; System.arraycopy(tmp, 0, res, 0, n); return res; } catch (Exception e) { throw new ParseException(e); } } }

The following example shows the BASE64 swap in use:

// Create a JSON serializer and register the BASE64 encoding swap with it. Serializer serializer = JsonSerializer.create().pojoSwaps(ByteArrayBase64Swap.class).build(); ReaderParser parser = JsonParser.create().pojoSwaps(ByteArrayBase64Swap.class).build(); byte[] a1 = {1,2,3}; String s1 = serializer.serialize(a1); // Produces "'AQID'" a1 = parser.parse(s1, byte[].class); // Reproduces {1,2,3} byte[][] a2 = {{1,2,3},{4,5,6},null}; String s2 = serializer.serialize(a2); // Produces "['AQID','BAUG',null]" a2 = parser.parse(s2, byte[][].class); // Reproduces {{1,2,3},{4,5,6},null}

It should be noted that the sample swaps shown above have already been implemented in the org.apache.juneau.transforms package. The following are a list of out-of-the-box swaps:

  • ByteArrayBase64Swap - Converts byte arrays to BASE64 encoded strings.
  • CalendarSwap - Swaps for converting Calendar objects to various date format strings.
  • DateSwap - Swaps for converting Date objects to various date format strings.
  • EnumerationSwap - Swaps for converting Enumeration objects to arrays.
  • IteratorSwap - Swaps for converting Iterator objects to arrays.
  • ReaderSwap - Swaps for converting Readers to objects before serialization.
  • XMLGregorianCalendarSwap - Swaps for converting XMLGregorianCalendar objects to ISO8601 strings.
Valid swapped class types

The swapped class type can be any serializable class type as defined in the POJO categories table.

1.3 - One-Way PojoSwaps

In the previous section, we defined two-way swaps, meaning swaps where the original objects could be reconstructing during parsing. However, there are certain kinds of POJOs that we may want to support for serializing, but that are not possible to reconstruct during parsing. For these, we can use one-way object swaps.

A one-way POJO swap is simply an object transform that only implements the swap() method. The unswap() method is simply left unimplemented.

An example of a one-way swaps would be one that allows Iterators to be serialized as JSON arrays. It can make sense to be able to render Iterators as arrays, but in general it's not possible to reconstruct an Iterator during parsing.

public class IteratorSwap extends PojoSwap<Iterator,List> { @Override public List swap(Iterator o) { List l = new LinkedList(); while (o.hasNext()) l.add(o.next()); return l; } }

Here is an example of our one-way swap being used. Note that trying to parse the original object will cause a ParseException to be thrown.

// Create a JSON serializer that can serialize Iterators. Serializer serializer = JsonSerializer.create().pojoSwaps(IteratorSwap.class).build(); // Construct an iterator we want to serialize. Iterator iterator = new ObjectList(1,2,3).iterator(); // Serialize our Iterator String s = serializer.serialize(iterator); // Produces "[1,2,3]" // Try to parse it. ReaderParser parser = JsonParser.create().pojoSwaps(IteratorSwap.class).build(); iterator = parser.parse(s, Iterator.class); // Throws ParseException!!!

1.4 - Stop Classes

Occasionally, you may want to limit bean properties to only those defined on a parent class or interface. There are a couple of ways of doing this.

For example, let's define the following parent class and subclass:

// Abstract parent class public abstract class MyClass { public String foo="foo"; } // Subclass 1 public class MyClassBar extends MyClass { public String bar="bar"; }

Suppose we only want to render the properties defined on MyClass, not those defined on child classes. To do so, we can define the following bean filter:

// Define transform that limits properties to only those defined on MyClass public class MyBeanFilter extends BeanFilter<MyClass> { public MyBeanFilter() { setInterfaceClass(MyClass.class); } }

When serialized, the serialized bean will only include properties defined on the parent class.

// Serialize to JSON WriterSerializer s = JsonSerializer.create().beanFilters(MyBeanFilter.class).build(); MyClass c = new MyClassBar(); String json = s.serialize(p); // Prints "{foo:'foo'}"

The equivalent can be done through an annotation on the parent class, which applies to all child classes:

@Bean(interfaceClass=MyClass.class) public abstract class MyClass { public String foo="foo"; }

The annotation can also be applied on the individual child classes, like so...

@Bean(interfaceClass=MyClass.class) public class MyClassBar extends MyClass { public String bar="bar"; }

Also, the beanFilters() methods will automatically interpret any non-BeanFilter classes passed in as meaning interface classes. So in the previous example, the BeanFilter class could have been avoided altogether by just passing in MyClass.class to the serializer, like so:

// Serialize to JSON WriterSerializer s = JsonSerializer.create().beanFilters(MyClass.class).build();

1.5 - Surrogate Classes

Surrogate classes are very similar in concept to one-way PojoSwaps except they represent a simpler syntax.

For example, let's say we want to be able to serialize the following class, but it's not serializable for some reason (for example, there are no properties exposed):

public class MyNonSerializableClass { protected String foo; }

This could be solved with the following PojoSwap.

public class MySerializableSurrogate { public String foo; } public class MySwap extends PojoSwap<MyNonSerializableClass,MySerializableSurrogate> { @Override public MySerializableSurrogate swap(MyNonSerializableClass o) { MySerializableSurrogate s = new MySerializableSurrogate(); s.foo = o.foo; return s; } }

However, the same can be accomplished by using a surrogate class that simply contains a constructor with the non-serializable class as an argument:

public class MySerializableSurrogate { public String foo; public MySerializableSurrogate(MyNonSerializableClass c) { this.foo = c.foo; } }

The surrogate class is registered in the same way as a PojoSwap:

// Create a JSON serializer that can serialize Iterators. Serializer serializer = JsonSerializer.create().pojoSwaps(MySerializableSurrogate.class).build();

When the serializer encounters the non-serializable class, it will serialize an instance of the surrogate instead.

1.6 - Serializing to ObjectMaps

A shortcut method for transforming is provided that can often be simpler than defining transforms. In this case, we add methods to our class to serialize to ObjectMaps

public class MyClass { private String f1; // Constructor that takes in an ObjectMap public MyClass(ObjectMap m) { f1 = m.getString("f1"); } // Method that converts object to an ObjectMap public ObjectMap toObjectMap() { return new ObjectMap().append("f1", f1); }

The toObjectMap() method will automatically be used during serialization, and the constructor will automatically be used during parsing. This will work for all serializers and parsers.

Skip navigation links

Copyright © 2017 Apache. All rights reserved.