Ted
Ted

Reputation: 20184

Deserializing JSON with Jackson - Why JsonMappingException "No suitable constructor"?

I have a problem deserializing a JSON string using Jackson (but I have no problem serializing an object to JSON).

Below I present the classes I use. The problem comes when I rececive a JSON-string (a ProtocolContainer that was serialized elsewhere and retrieved via webservice) and want to de-serialize it:

JSON-string:

{"DataPacketJSONString":null,"DataPacketType":"MyPackage.DataPackets.LoginRequestReply","MessageId":6604,"SenderUsername":null,"SubPacket":{"__type":"LoginRequestReply:#MyPackage.DataPackets","Reason":"Wrong pass or username","Success":false,"Username":"User1"}}

I try to deserialize like this:

ProtocolContainer ret = ProtocolContainer.Create(jsonString);

and the code that executes in ProtocolContainer can be seen below. The exception:

org.codehaus.jackson.map.JsonMappingException: No suitable constructor found for type [simple type, class MyPackage.ProtocolContainer]: can not instantiate from JSON object (need to add/enable type information?) at [Source: java.io.StringReader@4059dcb0; line: 1, column: 2]

ProtocolContainer.java - a container class that encapsulates my "SubPackets":

import java.io.IOException;

import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;

import MyPackage.DataPackets.*;

public class ProtocolContainer 
{
    public String SenderUsername;
    public String DataPacketType;
    public long MessageId;
    public String DataPacketJSONString;
    public DataPacket SubPacket;

    public ProtocolContainer(DataPacket dp)
    {
        DataPacketType = dp.getClass().toString().substring(6);
        SubPacket = dp;
    }

    public String toJSON()
    {
        try {
            if (SubPacket != null)
                this.DataPacketJSONString = ProtocolContainer.mapper.writeValueAsString(SubPacket);

            return ProtocolContainer.mapper.writeValueAsString(this);
        } catch (JsonGenerationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (JsonMappingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

    public static ObjectMapper mapper = new ObjectMapper();

    public static ProtocolContainer Create(String jsonString)
    {
        ProtocolContainer pc = null;
        try {
            pc = mapper.readValue(jsonString, ProtocolContainer.class); // error here!
        } catch (JsonParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (JsonMappingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();  // Exception when deserializing
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


        try 
        {
            if (pc != null && pc.DataPacketType == "LoginRequest")
                pc.SubPacket = mapper.readValue(jsonString, LoginRequest.class);
    }
        catch (JsonParseException e) 
        {
            e.printStackTrace();
        }
        catch (JsonMappingException e) 
        {
            e.printStackTrace();
        }
        catch (IOException e) 
        {
            e.printStackTrace();
        }
        return pc;
    }
}

DataPacket.java - a superclass for all my datapackets

public class DataPacket 
{

}

LoginRequestReply.java - a DataPacket

package MyPackage.DataPackets;

import MyPackage.DataPacket;

public class LoginRequestReply extends DataPacket
{
    public boolean LoginOK;
    public int UserId;
}

Upvotes: 24

Views: 96809

Answers (5)

Tiina
Tiina

Reputation: 4797

Another possibility if you use Lombok! I haven't found out the reason.

@Getter
@NoArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public Car implements Serializable {
   Map<String, Object> basicInfo;
   CarEnums.TypeEnum type;
   List<Maintenance> maintenances;
   public void addMaintenance(Maintenance m) {
      // initialize if null
      maintenances.add(m);
   }

   // must be static or jackson throws "inner class cannot be static" exception.  Yes you see it right. 
   public static class Maintenance { 
      private Long id;
      public class Maintenance(Long id) { // constructor causes the exception
         this.id = id;
      }
   }
   ...
}

If lombok constructor annotation is used at outer class, the inner class even if one writes all args constructor manually, it still complains the constructor cannot be found. If you use @AllArgsConstructor on Maintenance instead of writing your own, jackson would deserialise successfully. I got the same experience today, adding @AllArgsConstructor solves it.

Upvotes: 1

vks
vks

Reputation: 21

I was facing the issue and none of the answers worked for me. it seems the exception thrown is very generic one and is thrown for n number of causes. So one fix may not work for everyone. My case: we have a json response in which creditcard is a complex type but optional. when there is no creditcard data, we were getting an empty string in response:

"creditcard":""

But Credit card is a complex type for us:

<xs:element name="CC" minOccurs="0">
                                <xs:complexType>
                                    <xs:sequence>
                                        <xs:element name="aaa" type="xs:string" minOccurs="0"/>
                                        <xs:element name="bbb" type="xs:string" minOccurs="0"/>
                                        <xs:element name="ccc" type="xs:string" minOccurs="0"/>
                                    </xs:sequence>
                                </xs:complexType>
                            </xs:element>

We figured out, if there is no creditcard data, we should have something like this in json response:

"creditcard":{}

and not "creditcard":""

it fixed this issue.

Upvotes: 1

StaxMan
StaxMan

Reputation: 116630

In this case, you could add @JsonCreator annotation to constructor. There are two ways it could be done:

  • If you only add that annotation, then the whole matching JSON is first bound to type of the only argument (`DataPacket'). I assume you do not want to do that.
  • If you also add @JsonProperty annotation before the argument, then JSON property matching that name is passed to constructor (annotation is mandatory because Java byte code does NOT contain name of method or constructor arguments) -- I suspect you want @JsonProperty("SubPacket")

This works if necessary information for constructor comes from JSON. If not, you do need to add alternate no-arg constructor.

By the way, the error message does sound wrong in this case. It should only be given if JSON data matching expected value if a JSON String.

Upvotes: 15

Badal
Badal

Reputation: 4188

Thumb Rule: Add a default constructor for each class you used as a mapping class. You missed this and issue arise!

Simply add a default constructor and it should work.

Upvotes: 2

Ola Herrdahl
Ola Herrdahl

Reputation: 4296

The error messages says it all, your ProtocolContainer does not have a default constructor so Jackson is unable to create an instance of it. (Since the only current way of creating a ProtocolContainer is by passing in a DataPacket.)

Upvotes: 31

Related Questions