Mapping JSON to a Domain Model
Mapping class types directly to a JSON schema with annotations is always a preferable solution for (de)serialization as it's so straight forward. Jackson makes this even simpler and happens automatically without the need to annotate simple domain types, the parser simply uses class, method and field names to generate some suitable JSON. Where we don't wish to bind the JSON schema directly to concrete classes, or where some legacy code or schema must be mapped to a new domain structure we are forced to customize the mapping. This can be done in many ways and we could override the toString method of our domain classes and hard code the JSON schema. Separating the JSON schema away from concrete domain objects has obvious benefits, especially when using UI frameworks such as Angular which heavily depend on JSON from the server side. Mapping becomes difficult in this situation because we need to tell the parser what type of object to map to when it receives some data. Unless we include the class type name within the data, which would again bind our data to the model, we must tell Jackson how to find a suitable type to use.In this example a REST controller consumes and produces JSON which represents a user. The user is defined as an interface which is implemented in different domain model packages. This separates the controller from the underlying implementation and has clear advantages. However, Jackson must still know which concrete domain model object it is mapping the JSON to and this should be configurable so that the controller is not bound in any way to the model.
Defining the User interface is straight forward but we must annotate it to tell the parser where to find the identifier within the data structure and what functionality to use to determine the mapping.
Now we need to define the functionality to tell the Jackson parser how to resolve the class type. A possible solution would be to hard code the mapping somewhere within the domain model package or within the concrete type itself. But, what if we have multiple implementations of the model and wish to configure which type is mapped at runtime? Jackson provides the com.fasterxml.jackson.databind.jsontype.TypeIdResolver stereotype, and its abstract sub type TypeIdResolverBase specifically for this use case. In our case the UserTypeIdResolver extends the TypeIdResolverBase. There are two functions which must be implemented to allow Jackson to map the ID within the data to a specific class type.
Serialization
The TypeIdResolver enforces a function to generate the JSON ID property from the underlying concrete domain model type. The JSON property @type contains this value, as defined by the property attribute in the JsonTypeInfo annotation. The obvious format for this value would be the class name. However, this could be defined by some legacy schema or be something generic which doesn't relate to the Java domain model. In this example let's assume the property value is just 'User' and we'll formulate that value by trimming of the package name and any suffix from concrete class name assuming the usual class naming convention. In this example the User interface is implemented by com.johnhunsley.user.jpa.UserJpaImpl
The package name and class name suffix - 'JpaImpl' is predefined and injected into the resolver using the usual Spring property placeholder. Currently the Resolver is not a managed bean, it is instantiated by Jackson at runtime when the parser is invoked. Therefore, we must annotate the class with the @Configuration to tell Spring that instances of this class should be managed within the default context to allow configuration to be injected from the properties file.
The function to generate the ID value from the domain model class name boils down to simple String manipulation with some sanity checking to ensure the class name format fits the usual naming convention
The package name and class name suffix - 'JpaImpl' is predefined and injected into the resolver using the usual Spring property placeholder. Currently the Resolver is not a managed bean, it is instantiated by Jackson at runtime when the parser is invoked. Therefore, we must annotate the class with the @Configuration to tell Spring that instances of this class should be managed within the default context to allow configuration to be injected from the properties file.
The function to generate the ID value from the domain model class name boils down to simple String manipulation with some sanity checking to ensure the class name format fits the usual naming convention
Deserialization
The reverse operation is again enforced by the TypeIdResolver and is again a simple matter of String manipulation. In this case we construct a class name from the injected package name, suffix and ID value from the JSON property.Configuration
Spring Boot automatically provides a Property Placeholder Configurator which uses the default application.properties file from the class path. Therefore, configuration of the TypeIdResolver is a case of adding the domain package and suffix properties to this file.
So long as the com.johnhunsley.user.jpa.UserJpaImpl concrete domain type is on the class path Jackson will (de)serialize it from/to JSON which contains the @type:User value. There's no need to add any specific Jackson annotations or explicit mapping for this class
A complete example which uses this function for abstracting the mapping between an interface and concrete implementation can be found within the following repositories -
simple-user-account - Defines the User model interfaces
simple-user-account-jpa - Contains the concrete implementations of the model specifially for Jpa
simple-user-account-api - Defines the REST Controller and configuration for the application
really awesome blog
ReplyDeletehr interview questions
hibernate interview questions selenium interview questions
c interview questions
c++ interview questions
linux interview questions