24

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;
}
halfer
  • 19,824
  • 17
  • 99
  • 186
Ted
  • 19,727
  • 35
  • 96
  • 154
  • 1
    Follow-up: After some messing about, I am not getting the following error: *JsonMappingException: Can not instantiate value of type [simple type, class MyPackage.ProtocolContainer] from JSON String; no single-String constructor/factory method*. If I add a constructor that takes a String, then no error but the Object is "empty"... I dont think it should be necessary to add implementation support in the constructor. **How should I fix this?** – Ted Dec 03 '11 at 16:41

5 Answers5

31

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.)

Ola Herrdahl
  • 4,216
  • 3
  • 29
  • 23
  • 1
    yes, I added an empty constructor. Its weird, cause I got another error before, when I had an empty constructor... Anyways, this particular issue is resolved... no i have to deal with the "__type" thing that .NET adds on serialization... – Ted Dec 03 '11 at 12:05
  • Hmm, so the error is back, sort of. Now I get this: *JsonMappingException: Can not instantiate value of type [simple type, class MyPackage.ProtocolContainer] from JSON String; no single-String constructor/factory method*. If I add a constructor that takes a String, then no error but the Object is "empty"... I dont think it should be necessary to add implementation support in the constructor... – Ted Dec 03 '11 at 16:39
  • I forgot to put the default constructor, now it works, thanks ^^ – NonowPoney Oct 22 '14 at 22:34
15

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.

Eric
  • 6,563
  • 5
  • 42
  • 66
StaxMan
  • 113,358
  • 34
  • 211
  • 239
2

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.

Badal
  • 4,078
  • 4
  • 28
  • 28
  • None of the above answers are correct, the only thing that worked for me was public ConsolidationUserAndManagerCredentialsRequest() { super () ; } Without the `super()` nothing works. – Siddharth Aug 14 '15 at 04:11
1

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.

vks
  • 21
  • 4
1

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.

Tiina
  • 4,285
  • 7
  • 44
  • 73