In one of my project, I wanted to store the value of a domain object field as json string in database. Below is an example how it can be done.
Hibernate json user type
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.type.SimpleType
import groovy.transform.CompileStatic
import org.hibernate.engine.spi.SessionImplementor
import org.hibernate.usertype.ParameterizedType
import org.hibernate.usertype.UserType
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.SQLException
import java.sql.Types
@CompileStatic
class JsonUserType implements UserType, ParameterizedType {
private static final int[] SQL_TYPES = [Types.LONGVARCHAR] as int[]
private Class<?> returnedClass;
@Override
public boolean equals(Object x, Object y) throws HibernateException {
if (x == y) {
return true;
} else if (x == null || y == null) {
return false;
} else {
return x.equals(y);
}
}
@Override
public int hashCode(Object x) throws HibernateException {
return null == x ? 0 : x.hashCode();
}
@Override
public boolean isMutable() {
return true;
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
if(value == null) {
st.setNull(index, Types.VARCHAR)
} else {
String s = convertObjectToJson(value)
st.setString(index, s)
}
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
String value = rs.getString(names[0])
def result = null
if (value != null && !value.equals("")) {
try {
result = convertJsonToObject(value)
} catch (IOException e) {
throw new HibernateException("Exception deserializing value " + value, e);
}
}
return result;
}
Object convertJsonToObject(String content) {
if ((content == null) || (content.isEmpty())) {
return null;
}
try {
ObjectMapper mapper = new ObjectMapper()
mapper.enableDefaultTyping()
JavaType type = createJavaType(mapper)
if (type == null)
return mapper.readValue(content, returnedClass);
return mapper.readValue(content, type);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
String convertObjectToJson(Object object) {
try {
ObjectMapper mapper = new ObjectMapper()
mapper.enableDefaultTyping()
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper.writeValueAsString(object);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public Object deepCopy(Object value) throws HibernateException {
String json = convertObjectToJson(value);
return convertJsonToObject(json);
}
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return deepCopy(original);
}
@Override
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) deepCopy(value);
}
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return deepCopy(cached);
}
public JavaType createJavaType(ObjectMapper mapper) {
try {
return SimpleType.construct(returnedClass());
} catch (IllegalArgumentException e) {
return null;
}
}
@Override
public int[] sqlTypes() {
return SQL_TYPES;
}
@Override
public void setParameterValues(Properties parameters) {
this.returnedClass = Class.forName(parameters.getProperty('clazz'))
}
@Override
public Class<?> returnedClass() {
return this.returnedClass;
}
}
Gorm mapping for the json user type
Page {
PageBody body
static mapping = {
body type: JsonUserType, params: [clazz: PageBody.name]
}
static constraints = {
body nullable: true
}
}
Storing a list type field as json
class BlogPage extends Page {
List<Region> regions
static mapping = {
regions type: JsonUserType, params: [clazz: ArrayList.name]
}
static constraints = {
regions nullable: true
}
}
This is a quick and dirty implementation. There’s scope to improve it further. eg. You can modify it to take the advantage of database system’s native json column type if your database supports it.