Overview
The main entry point in Genson library is the Genson class. It provides methods to serialize Java objects to JSON and deserialize JSON streams to Java objects. Instances of Genson are immutable and thread safe, you should reuse them. In general the recommended way is to have a single instance per configuration type. Genson provides a default constructor with no arguments that uses the default configuration. To customize Genson have a look at the GensonBuilder.
Read/Write Json
Genson by default supports main Java classes such as primitives, Lists, Maps, Dates, etc. by providing Converters for them, if there is no such converter Genson will consider it is a Java Bean and will try to ser/de based on its Getters, Setters, Fields and Constructors.
Once you have a Genson instance, you can use it to serialize/deserialize JSON strings.
Supported IO classes
You can also use Java OutputStream/InputStream, Writer/Reader and byte arrays instead of Strings. In most cases you will want to use them instead of Strings for better performances.
Untyped Java structures
During serialization if there is no type information available (meaning it is Object), Genson will use the runtime type of the object to decide how to serialize it. If it has type information then it will use it instead of the runtime type.
In the example below, Genson will use the runtime type of the values in the map.
Deserialization however is more tricky as the type must be detected based on the content of the JSON stream. By default, if no type information is available during deserialization, Genson will provide a base mapping between JSON types and Java types. For example, JSON object will be mapped to a Map with Strings as keys, JSON arrays to Java List, etc. There is another mechanism allowing to deserialize to polymorphic or unknown types, but we will discuss it latter.
Here in both cases it will be deserialized to a map with the correct types (string and int) as they are standard Json types.
Note: Sometime you might want to always use the runtime type, this can be enabled with
POJO Databinding
Of course working only with Maps, Lists and primitive types is not so convenient. Usually your goal is to map the JSON to some structure you have defined and vice versa.
Genson provides full support for object databinding by following standard JavaBean conventions. The basic rules for databinding are:
- All public or package visibility fields, getters and setters will be used, including the inherited ones.
- If a getter/setter exists for a field then it will be used instead of the field (can be changed via configuration).
- Transient and static fields are not serialized nor used during deserialization.
- A default no argument constructor is required (can be configured to use constructors with arguments and support immutable types).
- If a field does not exist in the json stream then its field/setter will not be used (Genson can be configured to throw an exception).
- If a value is null then you can choose whether you want it to be serialized as null or just skipped, by default null values are serialized.
- Inherited annotations on methods/fields with same name are merged except JsonIgnore annotation which is a limitation for the moment.
- Annotations on getters and setters are not merged together, this allows to provide decoupled serialization and deserialization configurations. If you want to define some annotation to be used during both serialization and deserialization, then disable method resolution, enable private field resolution and annotate the fields.
Lets have a closer look to an example.
Remark Actually inner and anonymous classes serialization is supported but not deserialization, except if it is a static inner class.
You can also deserialize in an existing object. However this feature has some restrictions: it works only with Pojo like classes and does not handle nesting. For example if your root object contains a list or another Pojo, then it will be overriden by the one in the json (if present, otherwise it remains unchanged).
Filter/Rename properties
Genson provides two major ways of including/excluding and naming properties, via annotations and via configuration through GensonBuilder (not exclusive).
Via annotations
Excluding a property can be achieved by putting a @JsonIgnore
annotation on the field or getter/setter. The same goes for
including a property (for example a private field that you want to be serialized) but using @JsonProperty
annotation.
JsonProperty can also be used to define a different name for a property than the default one @JsonProperty(name="newName")
.
Via GensonBuilder
The other way is using the GensonBuilder exclude/include/rename methods that support a wider range of exclusion/inclusion/naming patterns. This feature gets handy if you can’t modify the source code of your classes or just don’t want to pollute your code with annotations.
For example suppose you have an attribute named password in some of your classes and want to always exclude it or that you want to always exclude properties of some type, etc. The code below demonstrates some of those strategies (but more are available - see the API).
Genson wide strategies
You can also define some strategies for property resolution that will be used for a Genson instance. You can choose whether to use Getter&Setter or Fields (or both - by default) and their visibility (public, protected, private…).
If you want to have a more general property resolution or naming strategy, you can respectively, extend PropertyBaseResolver or implement your own PropertyNameResolver and register it.
Excluding properties at runtime
One can also exclude a property at runtime by implementing a custom RuntimePropertyFilter and registering it via GensonBuilder. This filter would be used every time a property is being serialized or deserialized.
Object instantiation
By default Genson requires you to provide a default no argument constructor. But Genson also works with constructors that take arguments, encouraging people to work with immutable objects.
- Why by default doesn’t it support constructors with arguments?
Java reflection API does not provide access to parameter names of methods and constructors, Genson as most libs that deal with databinding uses it.
To bypass this problem Genson provides two solutions, annotations and automatic detection via byte code analysis.
Annotations
Annotate each parameter of the constructor with @JsonProperty, this would tell to Genson that this is the name that must be used.
Automatic resolution
You can also use constructors with arguments without using annotations. This mechanism is disabled by default as it uses byte code parsing to extract names from code that has been compiled with debug symbols (the default for most build systems and libs). This option does not work on Android.
Enable automatic name resolution for constructors and methods via the GensonBuilder.
Factory methods
Genson has also support for methods that act as factories. Those methods must be static, defined inside the class you want to
deserialize (or a parent) and annotated with @JsonCreator
annotation.
JsonCreator annotation can also be used on Constructors when you have multiple ones
and want to tell Genson which one to use.
In the previous example we could have a factory method that builds an Address with some default values, that would then be overridden by Genson based on the JSON content.
Exceptions
Since version 0.99 there are no checked exceptions anymore. The main reason is to avoid ugly boilerplate code for people who use Genson. Of course even if Genson makes use of unchecked exceptions you can still catch them if you want to handle them. Genson can throw two kind of exceptions:
- JsonStreamException, thrown by the streaming API when parsing for example invalid JSON.
- JsonBindingException, everywhere else, especially if something goes wrong at the databinding level, ex: can not instantiate some type.
Generic types
Due to type erasure, in Java, we can not do things like List<Address>.class. Meaning that we can’t tell to Genson to deserialize to a List of Addresses but only to a list of unknown objects. Those unknown objects being deserialized as Maps. The solution is to use what is called TypeToken, described in this blog. Gensons implementation of TypeToken is named GenericType. Why not TypeToken? Because this name is used in Guava library so choosing another name allows to avoid confusion and mistakes.
Genson has full support of Javas generics, allowing you to hava quite complex structures and still be able to ser/de it.
Remark that if in AddressGroup we were using Address type instead of the concrete type, the serialized json wouldn’t contain the street property.
Indeed it would have been ser/de using Address type, using the runtime type can be enable via new GensonBuilder().useRuntimeType(true)
.
Polymorphic types
Another nice feature of Genson is its ability to deserialize an object serialized with Genson back to its concrete type. Lets now enable runtime type resolution and serialize a Container of EuropeanAddress and then deserialize it back.
Of course in the previous example we could have deserialized to a new GenericType<Container<EuropeanAddress>>()
,
but if there were multiple implementations of Address or just mixed EuropeanAddress and Address instances, you won’t get the correct result.
The solution to this problem is to store in the serialized json the information about the concrete type. In Genson it is implemented via a metadata mechanism that will serialize class name + package during serialization and use it when deserializing back.
Genson provides an alias mechanism for classes, allowing to use the alias instead of the full class name. It is a good practice to do so as it gives you the ability to rename your class or package without any impact on the json (especially useful if you store json in a persistent storage) and also without leaking to the public the underlying code structure.
Remarks
-
Class metadata and the overall metadata mechanism is available only for object types and not for arrays or litteral values. Metadata must always be the first name/value pairs in json objects.
-
Genson does not serialize, nor use class metadata for standard types (lists, primitives, etc).
-
If you define a custom Converter and do not want Genson to use class metadata for types supported by your Converter use @HandleClassMetadata annotation.
-
Note that allowing deserialization to an arbitrary type based on the content of the JSON payload can come with security issues. For example the attacker could deserialize to any arbitrary type which Genson will instantiate and invoke its setters. There are classes available in common libraries and even in the JDK that can be used to gain Remote Code Execution upon invocation of a setter. Ideally this would be avoided by narrowing down what the types could be (so avoid defining the static type as Object or a list of anything).
Streaming API
Genson has lower memory consumption than DOM based parsers, because Genson does not need to store the complete JSON document in memory. This low memory footprint is being achieved via the Streaming API. As your objects are being serialized, the JSON is directly written to the output. Same during deserialization, as the JSON arrives in the stream, Genson will parse it and map to your objects.
This is a valuable feature in real life applications, like web applications or web services, where a client is writing/reading data through the network. The network is the most common bottleneck, paralleling the work while data is being transferred increases the throughput.
Genson databinding uses the streaming API, but you can also directly use it without the databinding layer in case where you really need something extremely fast, in most cases you don’t as it is already very efficient.
The main classes of the Streaming API are ObjectWriter and ObjectReader, they are internally used by Genson to write/read JSON data. When implementing your own Converters to achieve custom serialization/deserialization, you will use this low level API. But in some cases you might also want to use them directly without any databinding. You can build them through Genson class.
For example writing some object, containing an array and some basic properties can be easily done.
Reading back the previous structure is a bit more of code, mainly because of Java verbosity. Of course you can also use Genson to deserialize your JSON to a map and then just extract your properties from there. If you use this API in a Converter, then you might want to use JSR 353 types with Genson for intermediary representation and then map it to your structure. This would be OK, as it will be applied only to the part handled by your Converter, all the rest would still use directly the Streaming API.
Custom Ser/De
In some cases Genson may not serialize/deserialize a class the way you would like. First you should try to have a look at the different options available to you, such as Genson configuration via the GensonBuilder or annotations.
Custom Converter
If nothing seems to help solve your problem, then you might want to implement your-self the way a specific type is being ser/de. This is achieved through implementing a custom Converter. A Converter is mainly an implementation of a Serializer and Deserializer for a specific type. If you don’t need to provide a logic for serialization and deserialization, then you can implement only one of them.
Lets move the previous code in a datastructure and handle it in our Converter.
Like for the property childrenAges, in practice when implementing your own Converter, you will probably have to deal with more complex structures. Delegating to Genson the ser/de of complex types that you don’t need to handle can greatly reduce the amount of code.
You can also create a custom Serializer and Deserializer if you want to handle only serialization or deserialization.
Converter Factory
Converters are registered for specific types. The one they have in their signature and additional ones that you can specify when registering the Converter.
But in some cases you may want to be able to register a Converter for a complete hierarchy of types, ie. for Person and all its subclasses. This is one of the reasons Converter Factory has been introduced in Genson. The main role of a Factory is to return a Converter instance for a given type or null if the Factory does not handle this type.
Contextual Converter
Another nice feature in Genson is the support of Contextual Converters and Factories.
The contextual converter is used through @JsonConverter
annotation,
that you put on object properties (fields, methods, constructor arguments).
This will tell Genson to use this specific Converter for that property instead of any other.
You can also implement a ContextualFactory
to create Converters based on the property
- type
- name
- annotations
- the class in which the property is declared.
This can become really handy if you want to apply some custom Converters based on such criteria.
The @JsonConverter
and @JsonDateFormat
support have been implemented using Contextual Factories.
For example if you want to use some specific converter only when some custom annotation is present on the property:
A couple of other things & components can be customized, but they are a even more advanced usage and require some knowledge of how Genson works, thus they are not documented here. If you encounter any problems or don’t know how to achieve something, don’t hesitate to post a message on the mailling list. You can also get help there on how to extend more “internal” parts of Genson.