Jackson Tips: extracting Bean properties; encoding/decoding Base64
Due to extensive functionality Jackson offers for data binding in general (and JSON in particular), it is easy to get lost on everything that is available. It is also easy to miss possibilities to creatively use Jackson for non-JSON use cases; uses cases where no JSON is read or written.
I have written a bit about general data conversions with Jackson ("Not your type? Jackson as the match-maker"), but thought it might make sense to have another look. Not so much to extend what can be done, but to rather focus on just two things that I have found commonly useful.
1. Extracting (or injecting!) Bean Properties
One relatively common task -- or thing that would be useful to do, if it was easy -- is to take all logical properties of a bean, and expose them as a Map: this is needed to build all kinds of things, from templating (like JSTL bean accessors) to data conversions or diagnostic output. There are packages to do this, of course (like Commons bean-utils), but it is good to know that Jackson can do this rather well too:
// mad props to my map! Map<String,Object> properties = new ObjectMapper().convertValue(pojo, Map.class);
And there you have it: a Map with property name as key, and value "native" representation of property; a Map, List, Boolean, Integer/Long, Float/Double or null (referenced POJOs being recursively "serialized" as one of these types, as necessary). And then you can do whatever is necessary: access by name to use as replacement, add/remove properties, iterate over properties.
But it gets more interesting: given that Jackson 1.6 introduced new functionality to do "partial" deserialization -- that is, ability to change properties of an existing POJO -- we can do just that. In fact, we could use property Map from the first example: assuming we modified entries (maybe lower-cased all String values? Doubled numeric values, whatever), we can now modify the original POJO (or any other Java object that has set of properties we want to change). We could do it by:
// want to inject properties in a POJO? Use updatingReader! First create JsonNode (tree) from properties map ObjectMapper mapper = new ObjectMapper(); JsonNode propTree = mapper.convertValue(properties, JsonNode.class); // and then read in, to update pojo we gave! mapper.updatingReader(pojo).readValue(propTree);
Of course if we just wanted to instantiate a new POJO, it would be simple matter of:
BeanType bean = mapper.convertValue(properties, BeanType.class);
2. Encoding binary data as Base64 text, decoding
Now on to something totally different. Base64 encoding is commonly needed to provide things like, say, OAuth digest values, or security digests. There are specific external libraries available (as well as unofficial components from JDK that one may be tempted to use); but here too Jackson is actually a pretty good alternative: it already natively supports encoding and decoding of byte arrays as Base64 encoded JSON Strings. About the only caveat is that all JSON Strings are contained in double-quotes, which may need to strip out from results, or add around input String. More on this below.
So to encode binary data as Base64 String, we will do:
// First: encode single-byte array ObjectMapper mapper = new ObjectMapper(); byte binary = new byte; // five zero bytes // two ways to do it, actually; with one important difference: String quotedEncoded = mapper.writeValueAsString(binary); // WILL include surrounding double-quotes to be valid JSON // or String rawEncoded = mapper.convertValue(binary, String.class); // will only contain encoded String, NO double-quotes! // meaning, you get either "AAAAAAA=" // or AAAAAAA=
most often it probably makes sense to use 'convertValue()'. Similarly, when decoding base64-encoded Strings back to underlying binary data (byte array), you can do either:
byte data = mapper.readValue(quotedEncoded, byte.class); // if data is surrounded by quotes // or, if no quotes: data = mapper.convertValue(encoded, byte.class);
Pretty simple, convenient?