Serializing Collator instance - java

I am trying to serialize some objects of class InstitutionResultView which is basically wrapper to guava's TreeMultimap:
import java.io.Serializable;
import java.text.Collator;
import java.util.Comparator;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.collect.Maps.EntryTransformer;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Ordering;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.TreeMultimap;
public class InstitutionResultView implements Serializable {
private static final long serialVersionUID = -8110992296090587657L;
private final SortedSetMultimap<String, Institution> nameToInstitutionsMapping = TreeMultimap.create(
Ordering.from(StringComparators.AS_IS), // insertion order
Ordering.natural() // <----- this works when serializing object
// Ordering.from(Collator.getInstance()) // <----- when used instead previous line gives an exception when serializing
.nullsFirst().onResultOf(StringInstitutionFunctions.BY_NAME) // sort by name
.compound(
Ordering.natural().nullsFirst().onResultOf(IntegerInstitutionFunctions.BY_ID) // sort by id
));
public SortedSetMultimap<String, Institution> institutions() {
return nameToInstitutionsMapping;
}
public void setInstitutions(final Multimap<String, Institution> institutions) {
this.nameToInstitutionsMapping.clear();
this.nameToInstitutionsMapping.putAll(institutions);
}
#Override
public String toString() {
return Objects.toStringHelper(this)
.add("nameToInstitutionsMapping", Multimaps.transformEntries(nameToInstitutionsMapping, EntryTransformers.TO_NAME_WITH_CATEGORY))
.toString();
}
During serialization I get exception:
java.io.NotSerializableException: java.text.RuleBasedCollator at
java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1164)
at
...
com.google.common.collect.TreeMultimap.writeObject(TreeMultimap.java:180)
...
I found this bug from Sun's bug database which covers very similar case to mine, but it doesn't point to any resolution. I tried adding transient Collator instance field:
private transient Collator collatorInstance = Collator.getInstance();
and using it as Ordering.from(collatorInstance) but still it doesn't work.
I'd be glad if anybody could give me some idea what to do to solve this problem.
EDIT - That's what worked for me (thanks to #Puce and his answer):
class CollatorWrapper implements Comparator<String>, Serializable {
private static final long serialVersionUID = 1L;
private transient Collator collatorInstance;
public CollatorWrapper() {
super();
initCollatorInstance();
}
#Override
public int compare(final String o1, final String o2) {
return collatorInstance.compare(o1, o2);
}
private void initCollatorInstance() {
collatorInstance = Collator.getInstance();
}
private void writeObject(final java.io.ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
}
private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
initCollatorInstance();
}
}

I think you might be able to write a wrapper, which
implements Comparator
implements Serialzable (and the necessary methods such as readObject, writeObject)
wraps a transient Collator
remembers the Locale (if you need that)

Related

Jackson: add suffix according to a field type

Here my POJO:
public class AutorDenormalized {
private String id;
private Long unitatId;
private String grupId;
private String descripcio;
public AutorDenormalized() {
}
// getters $ setters
}
I'd like to serialise this kind of objects adding a suffix according to field type. I mean,
If field type is a String -> then add a *_s suffix
If field type is a Long -> then add a *_l suffix
Otherwise keep going
Do you have any ideas how to solve it?
You need to implement custom BeanPropertyWriter which can generate property name with a suffix. To register custom BeanPropertyWriter you need to create custom BeanSerializerModifier.
Below example shows simplified implementation which shows a way how to achieve above result:
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.databind.util.NameTransformer;
import java.io.IOException;
import java.util.List;
public class JsonTypeInfoApp {
public static void main(String[] args) throws IOException {
SimpleModule typeSuffixModule = new SimpleModule();
typeSuffixModule.setSerializerModifier(new TypeSuffixBeanSerializerModifier());
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.registerModule(typeSuffixModule);
System.out.println(mapper.writeValueAsString(new AutorDenormalized()));
}
}
class TypeSuffixBeanSerializerModifier extends BeanSerializerModifier {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
for (int i = 0; i < beanProperties.size(); ++i) {
final BeanPropertyWriter writer = beanProperties.get(i);
Class<?> rawType = writer.getType().getRawClass();
if (supports(rawType)) {
final String suffix = constructSuffix(rawType);
beanProperties.set(i, writer.rename(NameTransformer.simpleTransformer(null, suffix)));
}
}
return beanProperties;
}
private String constructSuffix(Class<?> rawType) {
return "_" + Character.toLowerCase(rawType.getSimpleName().charAt(0));
}
private boolean supports(Class<?> rawClass) {
return rawClass == String.class || rawClass == Long.class;
}
}
Above code prints:
{
"id_s" : "1",
"unitatId_l" : 123,
"grupId_s" : "2",
"descripcio_s" : "3"
}
See also:
Jackson custom serialization and deserialization
Aside from the accepted answer, which works fine, you could also consider implementing PropertyNameStrategy: it would let you rename properties and gets field, setter/getter, creator parameter (which you need to find type of property). Might be little bit less work.

Generic enum JPA AttributeConverter implementation

Problem I am trying to solve
I am trying to implement enum mapping for Hibernate. So far I have researched available options, and both the #Enumerated(EnumType.ORDINAL) and #Enumerated(EnumType.STRING) seemed inadequate for my needs. The #Enumerated(EnumType.ORDINAL) seems to be very error-prone, as a mere reordering of enum constants can mess the mapping up, and the #Enumerated(EnumType.STRING) does not suffice too, as the database I work with is already full of values to be mapped, and these values are not what I would like my enum constants to be named like (the values are foreign language strings / integers).
Currently, all these values are being mapped to String / Integer properties. At the same time the properties should only allow for a restricted sets of values (imagine meetingStatus property allowing for Strings: PLANNED, CANCELED, and DONE. Or another property allowing for a restricted set of Integer values: 1, 2, 3, 4, 5).
My idea was to replace the implementation with enums to improve the type safety of the code. A good example where the String / Integer implementation could cause errors is String method parameter representing such value - with String, anything goes there. Having an Enum parameter type on the other hand introduces compile time safety.
My best approach so far
The only solution that seemed to fulfill my needs was to implement custom javax.persistence.AttributeConverter with #Converter annotation for every enum. As my model would require quite a few enums, writing custom converter for each of them started to seem like a madness really quickly. So I searched for a generic solution to the problem -> how to write a generic converter for any type of enum. The following answer was of big help here: https://stackoverflow.com/a/23564597/7024402. The code example in the answer provides for somewhat generic implementation, yet for every enum there is still a separate converter class needed. The author of the answer also continues:
"The alternative would be to define a custom annotation, patch the JPA provider to recognize this annotation. That way, you could examine the field type as you build the mapping information and feed the necessary enum type into a purely generic converter."
And that's what I think I would be interested in. I could, unfortunately, not find any more information about that, and I would need a little more guidance to understand what needs to be done and how would it work with this approach.
Current Implementation
public interface PersistableEnum<T> {
T getValue();
}
public enum IntegerEnum implements PersistableEnum<Integer> {
ONE(1),
TWO(2),
THREE(3),
FOUR(4),
FIVE(5),
SIX(6);
private int value;
IntegerEnum(int value) {
this.value = value;
}
#Override
public Integer getValue() {
return value;
}
}
public abstract class PersistableEnumConverter<E extends PersistableEnum<T>, T> implements AttributeConverter<E, T> {
private Class<E> enumType;
public PersistableEnumConverter(Class<E> enumType) {
this.enumType = enumType;
}
#Override
public T convertToDatabaseColumn(E attribute) {
return attribute.getValue();
}
#Override
public E convertToEntityAttribute(T dbData) {
for (E enumConstant : enumType.getEnumConstants()) {
if (enumConstant.getValue().equals(dbData)) {
return enumConstant;
}
}
throw new EnumConversionException(enumType, dbData);
}
}
#Converter
public class IntegerEnumConverter extends PersistableEnumConverter<IntegerEnum, Integer> {
public IntegerEnumConverter() {
super(IntegerEnum.class);
}
}
This is how I was able to achieve the partially generic converter implementation.
GOAL: Getting rid of the need to create new converter class for every enum.
Luckily, you should not patch the hibernate for this.
You can declare an annotation like the following:
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.sql.Types;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Target({METHOD, FIELD})
#Retention(RUNTIME)
public #interface EnumConverter
{
Class<? extends PersistableEnum<?>> enumClass() default IntegerEnum.class;
int sqlType() default Types.INTEGER;
}
A hibernate user type like the following:
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Objects;
import java.util.Properties;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.DynamicParameterizedType;
import org.hibernate.usertype.UserType;
public class PersistableEnumType implements UserType, DynamicParameterizedType
{
private int sqlType;
private Class<? extends PersistableEnum<?>> clazz;
#Override
public void setParameterValues(Properties parameters)
{
ParameterType reader = (ParameterType) parameters.get(PARAMETER_TYPE);
EnumConverter converter = getEnumConverter(reader);
sqlType = converter.sqlType();
clazz = converter.enumClass();
}
private EnumConverter getEnumConverter(ParameterType reader)
{
for (Annotation annotation : reader.getAnnotationsMethod()){
if (annotation instanceof EnumConverter) {
return (EnumConverter) annotation;
}
}
throw new IllegalStateException("The PersistableEnumType should be used with #EnumConverter annotation.");
}
#Override
public int[] sqlTypes()
{
return new int[] {sqlType};
}
#Override
public Class<?> returnedClass()
{
return clazz;
}
#Override
public boolean equals(Object x, Object y) throws HibernateException
{
return Objects.equals(x, y);
}
#Override
public int hashCode(Object x) throws HibernateException
{
return Objects.hashCode(x);
}
#Override
public Object nullSafeGet(ResultSet rs,
String[] names,
SharedSessionContractImplementor session,
Object owner) throws HibernateException, SQLException
{
Object val = null;
if (sqlType == Types.INTEGER) val = rs.getInt(names[0]);
if (sqlType == Types.VARCHAR) val = rs.getString(names[0]);
if (rs.wasNull()) return null;
for (PersistableEnum<?> pEnum : clazz.getEnumConstants())
{
if (Objects.equals(pEnum.getValue(), val)) return pEnum;
}
throw new IllegalArgumentException("Can not convert " + val + " to enum " + clazz.getName());
}
#Override
public void nullSafeSet(PreparedStatement st,
Object value,
int index,
SharedSessionContractImplementor session) throws HibernateException, SQLException
{
if (value == null) {
st.setNull(index, sqlType);
}
else {
PersistableEnum<?> pEnum = (PersistableEnum<?>) value;
if (sqlType == Types.INTEGER) st.setInt(index, (Integer) pEnum.getValue());
if (sqlType == Types.VARCHAR) st.setString(index, (String) pEnum.getValue());
}
}
#Override
public Object deepCopy(Object value) throws HibernateException
{
return value;
}
#Override
public boolean isMutable()
{
return false;
}
#Override
public Serializable disassemble(Object value) throws HibernateException
{
return Objects.toString(value);
}
#Override
public Object assemble(Serializable cached, Object owner) throws HibernateException
{
return cached;
}
#Override
public Object replace(Object original, Object target, Object owner) throws HibernateException
{
return original;
}
}
And then, you can use it:
import org.hibernate.annotations.Type;
#Entity
#Table(name="TST_DATA")
public class TestData
{
...
#EnumConverter(enumClass = IntegerEnum.class, sqlType = Types.INTEGER)
#Type(type = "com.example.converter.PersistableEnumType")
#Column(name="INT_VAL")
public IntegerEnum getIntValue()
...
#EnumConverter(enumClass = StringEnum.class, sqlType = Types.VARCHAR)
#Type(type = "com.example.converter.PersistableEnumType")
#Column(name="STR_VAL")
public StringEnum getStrValue()
...
}
See also the chapter 5.3.3 Extending Hibernate with UserTypes at the excellent book "Java Persistence with Hibernate" by Bauer, King, Gregory.
Simplifying:
import com.pismo.apirest.mvc.enums.OperationType;
import com.pismo.apirest.mvc.enums.support.PersistableEnum;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
#SuppressWarnings("unused")
public interface EnumsConverters {
#RequiredArgsConstructor
abstract class AbstractPersistableEnumConverter<E extends Enum<E> & PersistableEnum<I>, I> implements AttributeConverter<E, I> {
private final E[] enumConstants;
public AbstractPersistableEnumConverter(#NonNull Class<E> enumType) {
enumConstants = enumType.getEnumConstants();
}
#Override
public I convertToDatabaseColumn(E attribute) {
return Objects.isNull(attribute) ? null : attribute.getId();
}
#Override
public E convertToEntityAttribute(I dbData) {
return fromId(dbData, enumConstants);
}
public E fromId(I idValue) {
return fromId(idValue, enumConstants);
}
public static <E extends Enum<E> & PersistableEnum<I>, I> E fromId(I idValue, E[] enumConstants) {
return Objects.isNull(idValue) ? null : Stream.of(enumConstants)
.filter(e -> e.getId().equals(idValue))
.findAny()
.orElseThrow(() -> new IllegalArgumentException(
String.format("Does not exist %s with ID: %s", enumConstants[0].getClass().getSimpleName(), idValue)));
}
}
#Converter(autoApply = true)
class OperationTypeConverter extends AbstractPersistableEnumConverter<OperationType, Integer> {
public OperationTypeConverter() {
super(OperationType.class);
}
}
}
I tried 1000 times create something same.
Generate converter for each enum on the fly - not problem, but then they will be have same class. Main problem there: org.hibernate.boot.internal.MetadataBuilderImpl#applyAttributeConverter(java.lang.Class<? extends javax.persistence.AttributeConverter>, boolean).
If converter already registered we got exception.
public void addAttributeConverterInfo(AttributeConverterInfo info) {
if ( this.attributeConverterInfoMap == null ) {
this.attributeConverterInfoMap = new HashMap<>();
}
final Object old = this.attributeConverterInfoMap.put( info.getConverterClass(), info );
if ( old != null ) {
throw new AssertionFailure(
String.format(
"AttributeConverter class [%s] registered multiple times",
info.getConverterClass()
)
);
}
}
Perhaps we can change org.hibernate.boot.internal.BootstrapContext Impl, but I'm sure it's create too complex and non-flexible code.

Error converting Java interface to Json schema

I am trying to convert a java interface to json schema but it is giving NullPointerException
public interface Contributors {
public List<Contributor> contributors();
public interface Contributor {
public String name();
public String contributorUrl();
public List<String> roles();
}
}
Edit 2:
I am getting the following output:
{"type":"object","$schema":"http://json-schema.org/draft-04/schema#"}
Edit 3:
Following is the code of SchemaGeneratorTest
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.github.reinert.jjschema.exception.TypeException;
import com.github.reinert.jjschema.v1.JsonSchemaFactory;
import com.github.reinert.jjschema.v1.JsonSchemaV4Factory;
public class SchemaGeneratorTest {
private static ObjectMapper mapper = new ObjectMapper();
public static final String JSON_$SCHEMA_DRAFT4_VALUE = "http://json-schema.org/draft-04/schema#";
public static final String JSON_$SCHEMA_ELEMENT = "$schema";
static {
// required for pretty printing
mapper.enable(SerializationFeature.INDENT_OUTPUT);
}
public static void main(String[] args) throws JsonProcessingException, TypeException {
JsonSchemaFactory schemaFactory = new JsonSchemaV4Factory();
schemaFactory.setAutoPutDollarSchema(true);
JsonNode productSchema = schemaFactory.createSchema(Contributors.class);
System.out.println(productSchema);
}
}
The library you are using only reports fields and getters in your schema. Rename your methods to begin with get:
public interface Contributors {
public List<Contributor> getContributors();
}
public interface Contributor {
public String getName();
public String getContributorUrl();
public List<String> getRoles();
}
EDIT: If you can't modify the interfaces, you can use this code to corrupt the "get" string and get it to print all methods anyway. Please don't use it in real production code, as you will cause yourself a lot of trouble.
public class Test {
private static boolean isCorrupted() {
return "haha".startsWith("get");
}
public static void main(String[] args) throws Exception {
String get = "get";
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
value.set(get, new char[]{});
System.out.println(isCorrupted()); // prints true
}
}

How do you serialize Guava's immutable collections using Protostuff?

I use protostuff-runtime to serialize object graphs. Some of these objects have reference to Guava immutable collections, such as ImmutableList and ImmutableSet. Protostuff is unable to deserialize these collections out of the box, because it tries to construct an instance and then "add" elements to it from the inputStream (which fails, since the collections are immutable).
Do you know of any library / protostuff plugin that does that out of the box? If not, is there a best practice to do this myself?
I've investigated, and found that protostuff has a concept of "delegate", that lets you take control of the serialization for specific types. It seems to be the answer to my problem, but I can't seem to get it working.
Here is what I have right now:
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
/**
* This is the POJO I want to serialize. Note that the {#code strings} field refers to an {#link ImmutableList}.
*/
#Immutable
public class Foo {
public static final Schema<Foo> SCHEMA = RuntimeSchema.getSchema(Foo.class);
#Nonnull
private final ImmutableList<String> strings;
public Foo(ImmutableList<String> strings) {
this.strings = Preconditions.checkNotNull(strings);
}
#Nonnull
public ImmutableList<String> getStrings() {
return strings;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof Foo) {
Foo that = (Foo) obj;
return this.strings.equals(that.strings);
}
return false;
}
#Override
public int hashCode() {
return strings.hashCode();
}
}
import com.dyuproject.protostuff.*;
import com.dyuproject.protostuff.runtime.Delegate;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.util.ArrayList;
public class ImmutableListDelegate implements Delegate<ImmutableList<?>> {
private static final Schema<ArrayList> LIST_SCHEMA = RuntimeSchema.getSchema(ArrayList.class);
#Override
public WireFormat.FieldType getFieldType() {
return WireFormat.FieldType.MESSAGE;
}
#Override
public ImmutableList<?> readFrom(Input input) throws IOException {
ArrayList<?> list = LIST_SCHEMA.newMessage();
input.mergeObject(list, LIST_SCHEMA);
return ImmutableList.copyOf(list);
}
#Override
public void writeTo(Output output, int number, ImmutableList<?> value, boolean repeated) throws IOException {
ArrayList<?> list = Lists.newArrayList(value);
output.writeObject(number, list, LIST_SCHEMA, repeated);
LIST_SCHEMA.writeTo(output, list);
}
#Override
public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated) throws IOException {
throw new UnsupportedOperationException("TODO");
}
#Override
public Class<?> typeClass() {
return ImmutableList.class;
}
}
import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.runtime.DefaultIdStrategy;
import com.dyuproject.protostuff.runtime.RuntimeEnv;
import com.google.common.collect.ImmutableList;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ImmutableListDelegateTest {
#Before
public void before() {
// registers the delegate
if (RuntimeEnv.ID_STRATEGY instanceof DefaultIdStrategy) {
((DefaultIdStrategy) RuntimeEnv.ID_STRATEGY).registerDelegate(new ImmutableListDelegate());
}
}
#Test
public void testDelegate() throws IOException {
Foo foo = new Foo(ImmutableList.of("foo"));
Assert.assertEquals(foo, serializeThenDeserialize(foo));
}
private Foo serializeThenDeserialize(Foo fooToSerialize) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ProtostuffIOUtil.writeDelimitedTo(out, fooToSerialize, Foo.SCHEMA, buffer());
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
Foo fooDeserialized = Foo.SCHEMA.newMessage();
ProtostuffIOUtil.mergeDelimitedFrom(in, fooDeserialized, Foo.SCHEMA, buffer());
return fooDeserialized;
}
private LinkedBuffer buffer() {
return LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
}
}
The test fails with the following exception, which seems to mean that my delegate only deserializes null values:
java.lang.NullPointerException
at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:191)
at com.google.common.collect.SingletonImmutableList.<init>(SingletonImmutableList.java:40)
at com.google.common.collect.ImmutableList.asImmutableList(ImmutableList.java:305)
at com.google.common.collect.ImmutableList.copyFromCollection(ImmutableList.java:314)
at com.google.common.collect.ImmutableList.copyOf(ImmutableList.java:253)
at test.ImmutableListDelegate.readFrom(ImmutableListDelegate.java:25)
at test.ImmutableListDelegate.readFrom(ImmutableListDelegate.java:12)
at com.dyuproject.protostuff.runtime.RuntimeUnsafeFieldFactory$19$1.mergeFrom(RuntimeUnsafeFieldFactory.java:1111)
at com.dyuproject.protostuff.runtime.MappedSchema.mergeFrom(MappedSchema.java:188)
at com.dyuproject.protostuff.IOUtil.mergeDelimitedFrom(IOUtil.java:109)
at com.dyuproject.protostuff.ProtostuffIOUtil.mergeDelimitedFrom(ProtostuffIOUtil.java:151)
at test.ImmutableListDelegateTest.serializeThenDeserialize(ImmutableListDelegateTest.java:38)
at test.ImmutableListDelegateTest.testDelegate(ImmutableListDelegateTest.java:30)
Is this the right approach? What am I missing?
This is not a duplicate of the What is a Null Pointer Exception, and how do I fix it? question, which makes no sense. The fact that I mentioned that an NPE is thrown when trying to use a Protostuff delegate to de-serialize immutable collections, doesn't mean that this duplicates the "What is a NPE?" question in any way, shape, or form.
Everything looks fine and the
java.lang.NullPointerException
at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:191)
at com.google.common.collect.SingletonImmutableList.<init>(SingletonImmutableList.java:40)
says that you're trying to put null into an ImmutableList, which is forbidden. To be sure, inspect you list just before the failing line. Make sure your input json doesn't look like [null].

Jackson - How to convert complex JSON to object from map?

The following code might look complex but is actually simple (and is copy/paste compatible):
import java.io.IOException;
import java.util.Map;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonTest {
#Test
public void test_complex_json_to_object() throws JsonGenerationException, JsonMappingException, IOException {
final String json = "{\"source[longitude]\":[\"26.3843435\"],\"source[latitude]\":[\"174.78830093\"]}";
final ObjectMapper m = new ObjectMapper();
#SuppressWarnings("unchecked")
final Map<String, Object> json_map = m.readValue(json, Map.class);
m.convertValue(json_map, Data.class);
}
public static class Location {
public double latitude;
public double longitude;
}
public static class Data {
public Location location;
#JsonProperty(value = "source[longitude]")
public void setSource__longitude(final String[] data) { // Invoked.
System.err.println("longitude");
}
#JsonProperty(value = "source[latitude]")
public void setSource__latitude(final String[] data) { // Invoked.
System.err.println("latitude");
}
public void setLocation(final String[] data) { // NOT Invoked!
System.err.println("latitude");
}
}
}
The JSON string is fixed. I get that out of an external service as a map, thus the conversion to Map before converting to the Data object.
I'd like to have the latitude and longitude setters defined within the Location object, and not as I'm currently forced to (within Data).
Problem is, Jackson doesn't understand that source[longitude] is actually source.longitude.
Any way to solve it?
You need to do custom handling; either by writing custom JsonDeserializer. Your use case is rather esoteric, so there isn't out-of-box support.
It's kind of like having your own data format living inside JSON, so you need to your own handlers.

Categories