0

In my project, I have a class called Closet which holds a list of clothing called clothes. I have implemented the code to saved the information of Closet objects into a .json file which turns out like this:

{
  "clothes" : [ {
    "name" : "mypants",
    "type" : "pants",
    "color" : "red",
    "size" : 32.0,
    "needsWashing" : false
  }, {
    "name" : "myshirt",
    "type" : "shirt",
    "color" : "blue",
    "size" : 2.0,
    "needsWashing" : false
  } ],
  "numberOfClothing" : 2
}

However, a JsonProcessingException is thrown when I try to retrieve the Json file and convert it back into a closet.

Closet closet = getDefaultObjectMapper().readValue(Paths.get("./data/Closet.json")
                .toFile(), Closet.class);

I am a beginner java programmer and am unsure of how I can approach this issue. I have done some searching with creating custom deserializers, however, I'm unsure of how to implement the deserializer with my nested objects (Clothing nested in Closet) and the fact that there could be an arbitrary number of Clothing in the Closet object.

I have the same problem with another class called StyleBoard which is a list of Outfit (another class), and an Outfit, is a list of Clothing. An example of the jsonfile for StyleBoard written to .json file is

contains 2 outfits "shirt and pants" and "pants and socks" each with 2 Clothing objects

{
  "styleBoard" : [ {
    "clothes" : [ {
      "name" : "mypants",
      "type" : "pants",
      "color" : "blue",
      "size" : 32.0,
      "needsWashing" : false
    }, {
      "name" : "myshirt",
      "type" : "shirt",
      "color" : "blue",
      "size" : 0.0,
      "needsWashing" : false
    } ],
    "favorite" : false,
    "name" : "shirt and pants",
    "numberOfClothing" : 2
  }, {
    "clothes" : [ {
      "name" : "mypants",
      "type" : "pants",
      "color" : "blue",
      "size" : 32.0,
      "needsWashing" : false
    }, {
      "name" : "mysocks",
      "type" : "socks",
      "color" : "white",
      "size" : 3.0,
      "needsWashing" : false
    } ],
    "favorite" : false,
    "name" : "pants and socks",
    "numberOfClothing" : 2
  } ],
  "numberOfOutfits" : 2
}

This is the Error Message I am getting:

Exception in thread "main" java.lang.RuntimeException: Cannot construct instance of `model.Clothing` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (String)"{
  "clothes" : [ {
    "name" : "myshirt",
    "type" : "shirt",
    "color" : "black",
    "size" : 2.09,
    "needsWashing" : false
  }, {
    "name" : "mypants",
    "type" : "pants",
    "color" : "red",
    "size" : 32.0,
    "needsWashing" : false
  } ],
  "collectionSize" : 2
}"; line: 3, column: 5] (through reference chain: model.Closet["clothes"]->java.util.ArrayList[0])
    at persistence.Json.fromJson(Json.java:43)
    at persistence.Json.parseUserCloset(Json.java:104)
    at ui.ClosetApp.loadUser(ClosetApp.java:195)
    at ui.ClosetApp.runClosetApp(ClosetApp.java:173)
    at ui.ClosetApp.doLogin(ClosetApp.java:103)
    at ui.ClosetApp.processLoginCommand(ClosetApp.java:68)
    at ui.ClosetApp.runLogin(ClosetApp.java:49)
    at ui.ClosetApp.<init>(ClosetApp.java:26)
    at ui.Main.main(Main.java:5)

A problem that I noticed that might be causing this, is that when I write my Closet Object to a .json File, it adds an extra field "numberOfClothing" as you can see above. This is not part of my Closet constructor, however I do have a getter called getNumberOfClothing. When I change the name of this getter to getCollectionSize, this extra field added to my json becomes "collectionSize". Why is this extra field being created? How can I prevent it? I think this extra field is causing the problem with reconstructing the object from json.

2 Answers2

0

First, you need to create the classes based on your json. You need to create a Closet class.

Fyi, I use Lombok for conciseness i.e. remove the need of getters, setters, and toString methods. If you don't use Lombok, you must create the getters and setters as the Jackson library will make use of this getters and setters.

@Getter
@Setter
@ToString
@FieldDefaults(level= AccessLevel.PRIVATE)
public class Closet {
    // you need to create getter and setter method.
    // i use lombok so I don't need to create one
    List<Cloth> clothes = new ArrayList<>();
    Integer numberOfClothing;
}

Then a Cloth class:

@Getter
@Setter
@ToString
@FieldDefaults(level= AccessLevel.PRIVATE)
public class Cloth {
    String name;
    String type;
    String color;
    Float size;
    Boolean needsWashing;
}

if you need to name your class member different from the json field, you can annotate with @JsonProperty("json field name") e.g.

@JsonProperty("name")
String clothName;

Then the actual conversion code:

Closet closet = JsonUtil.fromJson(jsonStr, Closet.class);

where JsonUtil class contains this static method (using jackson):

public static <T> T fromJson(String jsonStr, Class<T> clazz){
    try {
        return new ObjectMapper().readValue(jsonStr, clazz);
    } catch (Exception e) {
        throw new RuntimeException(e.getMessage());
    }
}

You can get jackson library via maven dependency below:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.0</version>
</dependency>

For jackson tutorial, you can google it. Below is just one of the example: https://www.baeldung.com/jackson-object-mapper-tutorial

And I leave the Styleboard for you as the steps are exactly the same as the Closet class.

fauzimh
  • 594
  • 4
  • 16
  • How would the fromJson method change if I wanted to retrieve the object from a .json file? Or, how would I get a jsonstring from a .json File? – Kenny Cheng Aug 02 '20 at 03:05
  • See below to get json string from a path string: `String jsonStr = new String(Files.readAllBytes(Paths.get("./data/Closet.json")), StandardCharsets.UTF_8);` Refer to: https://stackoverflow.com/questions/326390/how-do-i-create-a-java-string-from-the-contents-of-a-file – fauzimh Aug 02 '20 at 03:17
  • I tried what you said, as I already had my Closet Class, Clothing Class, and StyleBoard Class made, however, it didn't seem to work and an exception is still being thrown. Would you be willing to look at my code through github? – Kenny Cheng Aug 02 '20 at 03:19
  • maybe you should use the full path "C:\\myproject\\\mysubpath\\\data\\Closet.json" instead of the relative path "./data/Closet.json" and try to print the file content before you convert to json i.e. before calling JsonUtil's method. – fauzimh Aug 02 '20 at 03:23
  • And can you provide the error msg of JsonProcessingException, Your Closet class and Clothing class? Are all Getters and Setters all public and with correct data types? Is json file format correct? (you can check via https://jsonlint.com/) – fauzimh Aug 02 '20 at 03:31
  • I have provided the error message and identified something that may be the cause of the problem at the bottom of my question! – Kenny Cheng Aug 02 '20 at 03:55
  • Jackson need you to have an empty constructor i.e. constructor with empty params. If you created no constructor, java will auto-create for you. However, if you created your own constructor with params, you must also create another constructor without param. This is coz java will not auto-create constructor with no params if you already created another constructor. – fauzimh Aug 02 '20 at 04:14
  • What do you mean an empty constructor? Do you mean an empty constructor in the Closet Class? Also, what would I name this constructor, would it be the same name as my own Constructor? – Kenny Cheng Aug 02 '20 at 04:16
0

The issue is with my Clothing class not having a constructor with empty parameters. I simply added another Constructor.

The issue with the additional unrecognized field "numberOfclothing" is due to my Closet and StyleBoard Classes both having "getters" for values that weren't part of a field but for the size of fields using the List type. I simply had to change the name of these methods to not include "get" as part of their name.