8

I have a problem using the Jackson serialization from json, how do I serialize from Collections.unmodifiableMap?

The error I get is:

com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of java.util.Collections$UnmodifiableMap, problem: No default constructor found

I wanted to use the SimpleAbstractTypeResolver from http://wiki.fasterxml.com/SimpleAbstractTypeResolver however I cannot get the inner class type Collections$UnmodifiableMap

Map<Integer, String> emailMap = newHashMap();
Account testAccount = new Account();
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, As.PROPERTY);
String marshalled ;
emailMap.put(Integer.valueOf(10), "bob@mail.com");
testAccount.setMemberEmails(emailMap);

marshalled = mapper.writeValueAsString(testAccount);
System.out.println(marshalled);
Account returnedAccount = mapper.readValue(marshalled, Account.class);
System.out.println(returnedAccount.containsValue("bob@mail.com"));


public class Account {
  private Map<Integer, String> memberEmails = Maps.newHashMap();

  public void setMemberEmails(Map<Integer, String> memberEmails) {
    this.memberEmails = memberEmails;
  }

  public Map<Integer, String> getMemberEmails() {
    return Collections.unmodifiableMap(memberEmails);
  }

Any ideas? Thanks in advance.

hartraft
  • 83
  • 2
  • 8

2 Answers2

3

Okay, you've run into an edge-ish type case with Jackson. The problem really is that the library will happily use your getter method to retrieve collection and map properties, and only falls back to instantiating these collections/maps if those getter methods return null.

This can fixed by a combination of @JsonProperty/@JsonIgnore annotations, with the caveat that the @class property in your JSON output will change.

Code example:

public class Account {
    @JsonProperty("memberEmails")
    private Map<Integer, String> memberEmails = Maps.newHashMap();

    public Account() {
        super();
    }

    public void setMemberEmails(Map<Integer, String> memberEmails) {
        this.memberEmails = memberEmails;
    }

    @JsonIgnore
    public Map<Integer, String> getMemberEmails() {
        return Collections.unmodifiableMap(memberEmails);
    }
}

If you serialize this class with your test code you will get the following JSON:

{
    "@class": "misc.stack.pojo.Account",
    "memberEmails": {
        "10": "bob@mail.com",
        "@class": "java.util.HashMap"
    }
}

Which will deserialize correctly.

Perception
  • 79,279
  • 19
  • 185
  • 195
  • Thanks Perception. This works perfectly for my purposes. Is there a way to have the same behaviour without falling back to annotations on the model class itself? – hartraft Jul 15 '13 at 08:52
  • @hartraft what you might want to use are Jackson Mix In's http://wiki.fasterxml.com/JacksonMixInAnnotations – leo Jul 30 '14 at 12:23
0

The first thing Jackson looks for is the default constructor. If you want to use a different constructor, you need to specify add @JsonCreator on it and @JsonProperty annotations on its parameters.

Since you don't have ability to add these annotations to Collections.UnmodifiableCollection you won't be able to deserialize it.

  1. You probably don't need to to use unmodifiable collection to be serialized. What is the point? You can do that when deserializing the object. Also deserialized object will be different from the one serialized (copy)

  2. If you really need that, you can simply write your own UnmodifiableCollection class. It is very simple, since it is just a wrapper of collection and delegates method invocations to the underlying collection

    public boolean isEmpty() { return c.isEmpty(); } // c is underlying collection
    

    except for modifying methods:

    public boolean add(E e) {
        throw new UnsupportedOperationException();
    }
    .....
    
  3. You could also call one of the methods before serialization to serialize array. When deserializing build you model object with creating collection from array and access this collection with getter returning unmodifiable collection:

    public Object[] toArray();
    public <T> T[] toArray(T[] a);
    
  4. You can add @JsonIgnore to your getter method in model and add @JsonValue to your memberEmails field.

  5. You can create another getter (private and with different name) and annotate it as @JsonValue for your field

BitwiseMan
  • 1,887
  • 13
  • 24
Tala
  • 8,888
  • 5
  • 34
  • 38
  • Thanks Tala! i looked into the example i put into the original question and this indeed doesnt have a problem serializing unmodifiableMap The ObjectMapper i have uses DefaultTyping which is causing my issue. I've updated the question and I can narrow my search criteria a bit further now – hartraft Jul 12 '13 at 12:25
  • I've added options 4 and 5. I would personally go with 4 – Tala Jul 12 '13 at 12:34