Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson JSON, Immutable Classes, and Interfaces

I am playing with the Jackson examples and am having some trouble getting deserialization to work with immutable classes and interfaces.

Below is my code:

package com.art.starter.jackson_starter;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;

import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
/**  * Hello world!  *  */ public class App  {
    public static void main( String[] args ) throws JsonGenerationException, JsonMappingException, IOException
    {
        System.out.println( "Hello World!" );

        AddressImpl.AddressBuilder builder = new AddressImpl.AddressBuilder();
        NameImpl.Builder nameBuilder = new NameImpl.Builder();
        UserImpl.Builder userBuilder = new UserImpl.Builder();


        Name name = nameBuilder.first("FirstName")
                  .last("LastName")
                  .build();

        Address address =  builder.setCity("TestCity")
               .setCountry("TestCountry")
               .setState("PA")
               .setStreet("TestAddress")
               .setZip(123)
               .build();      

        User user = userBuilder.address(address)
                 .gender(User.Gender.MALE)
                 .isVerified(true)
                 .userImage(new byte[5])
                 .build();

        System.out.println(address);        
        System.out.println(name);
        System.out.println(user);

        StringWriter sw = new StringWriter();
        ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(sw, user);
        System.out.println(sw);


       StringReader sr = new StringReader("{\"address\":{\"state\":\"PA\",\"country\":\"TestCountry\",\"street\":\"TestAddress\",\"city\":\"TestCity\",\"zip\":123},\"verified\":true,\"gender\":\"MALE\",\"userImage\":\"AAAAAAA=\"}");

       /* 
          This line throws the Exception           
       */
       User user2 = mapper.readValue(sr, UserImpl.class);

       System.out.println(user2);
    } }

package com.art.starter.jackson_starter;

import java.util.Arrays;

import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonProperty;

public final class UserImpl implements User
{
   private final Address address;
   private final Gender gender;
   private final byte[] userImage;
   private final boolean isVerified;

   public static class Builder
   {
      private Address address;
      private Gender gender;
      //      private Name name;
      private byte[] userImage;
      private boolean isVerified;

      public Builder address(Address address)
      {
         this.address = address;
         return this;
      }

      public Builder gender(Gender gender)
      {
         this.gender = gender;
         return this;
      }

      //      public Builder name(Name name)
      //      {
      //         this.name = name;
      //         return this;
      //      }

      public Builder userImage(byte[] userImage)
      {
         this.userImage = userImage;
         return this;
      }

      public Builder isVerified(boolean isVerified)
      {
         this.isVerified = isVerified;
         return this;
      }

      public UserImpl build()
      {
         return new UserImpl(address, gender, userImage, isVerified);
      }
   }

   @JsonCreator
   public UserImpl(@JsonProperty("address") Address address, @JsonProperty("gender") Gender gender, @JsonProperty("userImage") byte[] userImage,
         @JsonProperty("verified") boolean isVerified)
   {
      super();
      this.address = address;
      this.gender = gender;
      this.userImage = userImage;
      this.isVerified = isVerified;
   }

   public Address getAddress()
   {
      return address;
   }

   public Gender getGender()
   {
      return gender;
       }

   public byte[] getUserImage()
   {
      return userImage;
   }

   public boolean isVerified()
   {
      return isVerified;
   }

   @Override
   public String toString()
   {
      StringBuilder builder2 = new StringBuilder();
      builder2.append("UserImpl [address=");
      builder2.append(address);
      builder2.append(", gender=");
      builder2.append(gender);
      builder2.append(", isVerified=");
      builder2.append(isVerified);
      builder2.append(", name=");
      builder2.append(", userImage=");
      builder2.append(Arrays.toString(userImage));
      builder2.append("]");
      return builder2.toString();
   }

}

package com.art.starter.jackson_starter;

import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonProperty;

public final class AddressImpl implements Address
{
   private final String city;
   private final String country;
   private final String street;
   private final String state;
   private final int zip;

   public static class AddressBuilder
   {
      private String city;
      private String country;
      private String street;
      private String state;
      private int zip;

      public AddressBuilder setCity(String city)
      {
         this.city = city;
         return this;
      }

      public AddressBuilder setCountry(String country)
      {
         this.country = country;
         return this;
      }

      public AddressBuilder setStreet(String street)
      {
         this.street = street;
         return this;
      }

      public AddressBuilder setState(String state)
      {
         this.state = state;
         return this;
      }

      public AddressBuilder setZip(int zip)
      {
         this.zip = zip;
         return this;
      }

      public AddressImpl build()
      {
         return new AddressImpl(city, country, street, state, zip);
      }

   }

   @JsonCreator
   public AddressImpl(@JsonProperty("city") String city, @JsonProperty("country") String country, @JsonProperty("street") String street,
         @JsonProperty("state") String state, @JsonProperty("zip") int zip)
   {
      this.city = city;
      this.country = country;
      this.street = street;
      this.state = state;
      this.zip = zip;
   }

   public String getCity()
   {
      return city;
   }

   public String getCountry()
   {
      return country;
   }

   public String getStreet()
   {
      return street;
   }

   public String getState()
   {
      return state;
   }

   public int getZip()
   {
      return zip;
   }

   @Override
   public String toString()
   {
      StringBuilder builder = new StringBuilder();
      builder.append("AddressImpl [city=");
      builder.append(city);
      builder.append(", country=");
      builder.append(country);
      builder.append(", state=");
      builder.append(state);
      builder.append(", street=");
      builder.append(street);
      builder.append(", zip=");
      builder.append(zip);
      builder.append("]");
      return builder.toString();
   }

}

The issue appears to be with Address. I get this exception:

Exception in thread "main" org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.art.starter.jackson_starter.Address, problem: abstract types can only be instantiated with additional type information
 at [Source: java.io.StringReader@785f8172; line: 1, column: 2]
    at org.codehaus.jackson.map.JsonMappingException.from(JsonMappingException.java:163)
    at org.codehaus.jackson.map.deser.StdDeserializationContext.instantiationException(StdDeserializationContext.java:212)
    at org.codehaus.jackson.map.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:97)
    at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:230)
    at org.codehaus.jackson.map.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:595)
    at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:472)
    at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:350)
    at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2391)
    at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1614)
    at com.art.starter.jackson_starter.App.main(App.java:56)

I am sure this is because there is no way for Jackson to resolve Address which is an interface to AddressImpl which is a concrete implementation. I have been poking through the docs and have looked at a few articles regarding the @JsonDeserialize(as=AddressImpl.class),but it didn't work. So I am stumped. Has anyone ever gotten this to work, is it even supported?

It works like a champ if I replace Address with AddressImpl in the UserImpl class.

like image 718
nsfyn55 Avatar asked Jun 14 '11 14:06

nsfyn55


People also ask

Can Jackson serialize interface?

Jackson can serialize and deserialize polymorphic data structures very easily. The CarTransporter can itself carry another CarTransporter as a vehicle: that's where the tree structure is! Now, you know how to configure Jackson to serialize and deserialize objects being represented by their interface.

Is JSON object immutable?

JsonObject class represents an immutable JSON object value (an unordered collection of zero or more name/value pairs). It also provides unmodifiable map view to the JSON object name/value mappings. A JsonObject instance can be created from an input source using JsonReader. readObject() .

Can immutable class be serializable?

It turns out you can serialize immutable objects because there's no requirement that there be a public no-argument constructor.

Does Jackson need all args constructor?

11.2. Jackson won't use a constructor with arguments by default, you'd need to tell it to do so with the @JsonCreator annotation. By default it tries to use the no-args constructor which isn't present in your class.


1 Answers

Just in case you hadn't seen it, here's a blog entry that discusses working with immutable objects and Jackson.

But you should definitely be able to use @JsonDeserialize(as=AddressImpl.class); either by adding it to Address.java interface (either directly or by using mix-ins), or by adding it to field or property. One thing to note is that for deserialization, it MUST be next to accessor you use; setter if you have one, if not, next to field. Annotations are not (yet) shared between accessors; so for example adding it to 'getter' would not work.

Jackson 1.8 also finally allows registration of abstract-to-concrete types (see http://jira.codehaus.org/browse/JACKSON-464 for more details) which might be the best option to indicate that 'AddressImpl' is to be used for 'Address'.

like image 99
StaxMan Avatar answered Sep 20 '22 15:09

StaxMan