13

I am trying to make the Json output from Cucumber into a single Java object. This contains objects nested four levels deep, and I am having trouble deserializing it. I am presently using Jackson, but open to suggestions. Here is my Json code:

{
"line": 1,
"elements": [
  {
    "line": 3,
    "name": "Converteren centimeters naar voeten/inches",
    "description": "",
    "id": "applicatie-neemt-maten-in-cm-en-converteert-ze-naar-voet/inch,-en-vice-versa;converteren-centimeters-naar-voeten/inches",
    "type": "scenario",
    "keyword": "Scenario",
    "steps": [
      {
        "result": {
          "duration": 476796588,
          "status": "passed"
        },
        "line": 4,
        "name": "maak Maten-object aan met invoer in \"centimeters\"",
        "match": {
          "arguments": [
            {
              "val": "centimeters",
              "offset": 37
            }
          ],
          "location": "StepDefinition.maakMatenObjectAanMetInvoerIn(String)"
        },
        "keyword": "Given "
      },
      {
        "result": {
          "duration": 36319,
          "status": "passed"
        },
        "line": 5,
        "name": "ik converteer",
        "match": {
          "location": "StepDefinition.converteerMaten()"
        },
        "keyword": "When "
      },
      {
        "result": {
          "duration": 49138,
          "status": "passed"
        },
        "line": 6,
        "name": "uitvoer bevat maat in \"voeten/inches\"",
        "match": {
          "arguments": [
            {
              "val": "voeten/inches",
              "offset": 23
            }
          ],
          "location": "StepDefinition.uitvoerBevatMaatIn(String)"
        },
        "keyword": "Then "
      }
    ]
  },
  {
    "line": 8,
    "name": "Converteren voeten/inches naar centimeters",
    "description": "",
    "id": "applicatie-neemt-maten-in-cm-en-converteert-ze-naar-voet/inch,-en-vice-versa;converteren-voeten/inches-naar-centimeters",
    "type": "scenario",
    "keyword": "Scenario",
    "steps": [
      {
        "result": {
          "duration": 84175,
          "status": "passed"
        },
        "line": 9,
        "name": "maak Maten-object aan met invoer in \"voeten/inches\"",
        "match": {
          "arguments": [
            {
              "val": "voeten/inches",
              "offset": 37
            }
          ],
          "location": "StepDefinition.maakMatenObjectAanMetInvoerIn(String)"
        },
        "keyword": "Given "
      },
      {
        "result": {
          "duration": 23928,
          "status": "passed"
        },
        "line": 10,
        "name": "ik converteer",
        "match": {
          "location": "StepDefinition.converteerMaten()"
        },
        "keyword": "When "
      },
      {
        "result": {
          "duration": 55547,
          "status": "passed"
        },
        "line": 11,
        "name": "uitvoer bevat maat in \"centimeters\"",
        "match": {
          "arguments": [
            {
              "val": "centimeters",
              "offset": 23
            }
          ],
          "location": "StepDefinition.uitvoerBevatMaatIn(String)"
        },
        "keyword": "Then "
      }
    ]
  }
],
"name": "Applicatie neemt maten in cm en converteert ze naar voet/inch, en vice versa",
"description": "",
"id": "applicatie-neemt-maten-in-cm-en-converteert-ze-naar-voet/inch,-en-vice-versa",
"keyword": "Feature",
"uri": "sample.feature"
}

I have tried a number of different approaches. First I used nested inner classes, but it appeared you had to make them static, which I feared would not work since I have multiple instances of the same object within one (multiple "element"-objects in the root, for example). Then I tried putting them in separate classes, with Json annotations. Here's where that got me (omitting setters):

public class CucumberUitvoer {
    private String name;
    private String description;
    private String id;
    private String keyword;
    private String uri;
    private int line;
    @JsonProperty("elements")
    private List<FeatureObject> elements;

    public CucumberUitvoer(){}
}

public class FeatureObject {
    private String name;
    private String description;
    private String id;
    private String type;
    private String keyword;
    private int line;
    @JsonProperty("steps")
    private List<StepObject> steps;

    public FeatureObject() {
    }
}

public class StepObject {
    @JsonProperty("result")
    private ResultObject result;
    private String name;
    private String given;
    private String location;
    private String keyword;
    private int line;
    @JsonProperty("match")
    private MatchObject match;

    public StepObject(){}
}

public class ResultObject {
    private int duration;
    private String status;

    public ResultObject(){}
}

public class MatchObject {
    @JsonProperty("arguments")
    private List<ArgumentObject> arguments;
    private String location;

    public MatchObject(){}
}

public class ArgumentObject {
    private String val;
    private String offset;

    public ArgumentObject(){}
}

For clarification, here's a class diagram of how the nesting works.

This solution gives me the following error:

com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of nl.icaprojecten.TestIntegratieQuintor.JSONInterpreter.CucumberUitvoer out of START_ARRAY token

Here is the code doing the actual mapping:

ObjectMapper mapper = new ObjectMapper();
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    CucumberUitvoer obj1 = null;
    try {
        obj1 = mapper.readValue(json, CucumberUitvoer.class);
    } catch (IOException e) {
        e.printStackTrace();
    }

Is there a quick fix to this approach to make it work, or should I try something entirely different?

KeizerHarm
  • 330
  • 1
  • 4
  • 22
  • Have you tried adding getters and setters? The fields are not public, thus objects cannot be serialized into these classes – Turbut Alin Nov 29 '16 at 09:49
  • 1
    I thought only setters were required? I do have those, just omitted here for making the code more readable. – KeizerHarm Nov 29 '16 at 09:50
  • @TurbutAlin Adding getters didn't have any effect, I'm afraid. – KeizerHarm Nov 29 '16 at 10:01
  • @KeizerHarm in your Match entity you missed the location attribute – cralfaro Nov 29 '16 at 10:02
  • I usually use gson for this. No need for annotation if you name the variables the same as the objects in the json (could be the same for jackson though). If you define nested classes (subclass in the same .java file) you should indeed make them static, however that does not mean that instances of said nested class have to be static. I'm not sure why it is even possible to use non static nested (inner) classes, but you can, however this is not what you want here. – p.streef Nov 29 '16 at 10:04
  • @cralfaro Thanks, I added that one in. Error remains. – KeizerHarm Nov 29 '16 at 10:10
  • @KeizerHarm just a little clue, the problem is with the arguments array, i am trying to figure out why... – cralfaro Nov 29 '16 at 11:03
  • @KeizerHarm check my response, finally the solution was something very silly – cralfaro Nov 29 '16 at 11:30
  • Static classes doesn't mean you can't have multiple instances. – shmosel Apr 02 '18 at 20:00
  • @shmosel This was over a year ago, but thanks :) – KeizerHarm Apr 03 '18 at 10:05

3 Answers3

5

Ok I spent some time debugging and trying to figure out what was the problem, and finally was something pretty obvious.

implements Serializable

Thats the line I added to MatchObject and worked.

When we try to deserialize some object first we have to make those classes implements the interface Serializable

cralfaro
  • 5,822
  • 3
  • 20
  • 30
  • That's got me to make some actual progress, thanks! But I now have a new error: "java.lang.NoSuchMethodError: com.fasterxml.jackson.databind.JavaType.isReferenceType()Z" – KeizerHarm Nov 29 '16 at 11:44
  • @KeizerHarm probably you forgot your getter/setter or the type you are trying to deserialize is wrong, for example store "hi" into an integer attribute – cralfaro Nov 29 '16 at 11:47
1

I just tried your sample code and oddly, it works.

Can you please double check your imports, if the JSON is coming in as provided and the getters, setters, constructors are actually there?

Turbut Alin
  • 2,568
  • 1
  • 21
  • 30
  • With the package com.fasterxml.jackson is not working – cralfaro Nov 29 '16 at 11:11
  • Should the constructors be implemented with the this.var = var and all? I thought they were supposed to be empty. – KeizerHarm Nov 29 '16 at 11:17
  • either empty and setters or full constructors (set all fields) – Turbut Alin Nov 29 '16 at 11:19
  • I now have empty constructors and full setters and getters. Should I remove the setters and getters and add full constructors then? – KeizerHarm Nov 29 '16 at 11:20
  • Try this code sample: http://pastebin.com/iLCQT4un For me it prints: maak Maten-object aan met invoer in "centimeters" – Turbut Alin Nov 29 '16 at 11:21
  • I have the following dependencies: jackson-datatype-json-org, jackson-datatype-hppc, jackson-datatype-jsr310, jackson-datatype-hibernate4, jackson-annotations, jackson-databind and all version 2.6.3. – Turbut Alin Nov 29 '16 at 11:27
  • @TurbutAlin I am very confused now. Copy-pasting what you wrote to a new class named One made it work. But copy-pasting each of the classes and the mapping code to their equivalent places (while removing static from the classes) did not. – KeizerHarm Nov 29 '16 at 11:34
  • I just tried copy pasting each of the classes into different files and it still works. I was trying to debug the fact that they were static inner classes, but oddly, it still works. Also, check @cralfaro's answer. Implementing Serializable is also a must we forgot to mention. Try that method on your failing test case. – Turbut Alin Nov 29 '16 at 11:37
  • @TurbutAlin maybe he had the classes as inner classes and without Serializable thats why failed – cralfaro Nov 29 '16 at 11:40
  • @cralfaro & KeizerHarm what jackson versions are you using? Can you try with 2.6.3 and my testcases? I am trying to search into Jackson documentation, but cannot find anything related. – Turbut Alin Nov 29 '16 at 11:46
  • @TurbutAlin did exactly what was now suggested. Added Serializable to MatchObject, and I got a different error: `java.lang.NoSuchMethodError: com.fasterxml.jackson.databind.JavaType.isReferenceType()Z`. Perhaps I should make all the classes implement Serializable? Why only MatchObject? – KeizerHarm Nov 29 '16 at 11:46
  • All classes should implement Serializable. – Turbut Alin Nov 29 '16 at 11:47
  • as @TurbutAlin said all classes – cralfaro Nov 29 '16 at 11:48
  • Everything now implements Serializable. Still have that error. Weird thing is that the code was copied exactly from that working example, so with the same getters/setters/constructors/testcase/imports/data type assignment. – KeizerHarm Nov 29 '16 at 11:51
  • OP feel free to vote mine and @cralfaro's answers and comments which you think helped. Sorry to say this, but I feel new users always forget to do this :) – Turbut Alin Nov 29 '16 at 11:56
  • 1
    @TurbutAlin I did, but because I have less than 15 reputation they do not get shown sadly. – KeizerHarm Nov 29 '16 at 11:57
  • Then feel free to accept one answer, as we both showed you a working example. You have a starting point of debugging. I also pointed you to the Jackson version, which you can still check. Hope this helps. – Turbut Alin Nov 29 '16 at 12:00
  • 1
    I am very grateful for both of your help! I just did not yet want to 'close' the question yet because I wasn't sure if the new error was related to the old one. Jackson version is 2.6.3, as you specified. – KeizerHarm Nov 29 '16 at 12:04
  • Found the error. It was because of some extra []-brackets added in the json string in some places but not in others. Stupid thing, but it's fixed now. Thank you everyone! – KeizerHarm Nov 30 '16 at 10:18
-1

You can get the idea from this code to deserialize,

public class testCustomDeSerializer extends JsonDeserializer<test> {

public testCustomDeSerializer() {
    this(null);
}

public TestCustomDeSerializer(Class t) {
   // super(t);
}

@Override
public Test deserialize(JsonParser p, DeserializationContext ctx) throws IOException, JsonProcessingException {
    ObjectCodec objectCodec = p.getCodec();
    JsonNode node = objectCodec.readTree(p);

    ObjectMapper objectMapper =  new ObjectMapper();
    Test test= new Test();


    test.setId(node.get("line").asText());

    List<elements> elementList = new ArrayList<>();
    JsonNode elementsNode = node.get("elements");
    Iterator<JsonNode> slaidsIterator = elementsNode.elements();
    while (slaidsIterator.hasNext()) {
        Steps steps= new Steps();
        JsonNode slaidNode = slaidsIterator.next();
        JsonNode stepNode= (JsonNode) slaidNode.get("Steps");
        BoundingPoly in = objectMapper.readValue(stepNode.toString(), Steps.class);
        elementsNode.setSteps(in);
        /// continue

 return
        }

Hope it helps

Zubair Nabi
  • 1,016
  • 7
  • 28