0

I have a json file with users, each users has email and password and corresponding POJO exist to deserialize them.

I'm using both jackson and JSON.simple.

However, I want to add users at will, and create a method to find them by their description, let's say this is my json:

{
  "user1": {
    "email": "user1@user.com",
    "password": "qwe123",
  },
  "user2": {
    "email": "user2@user.com",
    "password": "abc123",
  },

...
  "userX": {
    "email": "userX@user.com",
    "password": "omg123",
  }
}

This is my POJO:

public record User(String email, String password) {}

I dont want to create superPOJO and add each user as I create them.

I would like to create method that would read my json, and returned the User object based on String input.

For now I created users in array and get them using their index, but now situation requires giving users "nicknames" and getting them by their nickname.

Now I am keeping user like this:

[
  {
    "email": "xxx@xxx.com",
    "password": "xxx111",
  },
  {
    "email": "yyy@yyy.com",
    "password": "yyy222",
  }
]

This is my current method:

public User getUser(int index) throws IOException {
    return Parser.deserializeJson(getUserFile(), User[].class)[index];
}

where Parser#deserializeJson() is:

public <T> T deserializeJson(String fileName, Class<T> clazz) throws IOException {
    return new ObjectMapper().readValue(Utils.reader(fileName), clazz);
}

And Utils.reader just brings file from the classpath.

I would like a method like this:

public User getUser(String nickname) throws IOException {
    return Parser.deserializeJson(getUserFile(), User.class);
}

and when calling this method with parameter nickname of user2 I'd get User object with fields: email user2@user.com and password abc123

hetacz
  • 107
  • 10

2 Answers2

2

Given your input json is an object (map) of User-like objects:

{
  "user1": {
    "email": "user1@user.com",
    "password": "qwe123",
  },
  "user2": {
    "email": "user2@user.com",
    "password": "abc123",
  },

...
  "userX": {
    "email": "userX@user.com",
    "password": "omg123",
  }
}

then you should be able to deserialize it into a (Hash)Map of Users:

public <T> Map<String, Class<T>> deserializeJson(String fileName) throws IOException {
    return new ObjectMapper().readValue(Utils.reader(fileName), new TypeReference<HashMap<String, Class<T>>>(){});
}

and then use it like so:

public User getPredefinedUser(String nickname) throws IOException {
    return Parser.deserializeJson(getUserFile(), User.class).get(nickname);
}

(Although you probably want to parse once and store that somewhere, not parse every time).

Thanks for the compile check and fix by hetacz. Had the templates wrong, and an unnecessary (for this case) class variable

Garrett Motzner
  • 3,021
  • 1
  • 13
  • 30
  • Your deserializeJson doesnt compile, I wrote sth like this: public Map> toMap(String fileName) throws IOException { return MAPPER.readValue(Utils.reader(fileName), new TypeReference>>(){}); } – hetacz Nov 23 '22 at 05:04
  • 1
    Thanks! Fixed it. I imagine that the `clazz` variable is unnecessary because it can be inferred by the return type? Haven't done java in a while :) – Garrett Motzner Dec 03 '22 at 00:03
  • I guess so, in my case I just completely changed it from public Map> to public Map, as I found, that I don't have other cases in my code for now, but I guess if I wanted to keep it generic and tried to deserialize something deserializable to another existing object, it would have worked. – hetacz Dec 03 '22 at 01:03
0

After putting some thought into my own problem I decided to do it like this (I also dropped using a JSON.simple library in the meantime, as per suggestion that using two for such a simple task is an overkill).

I am using com.fasterxml.jackson.core - Jackson Databind.

I used helper function from Parser class where MAPPER is a new ObjectMapper():

    public JsonNode toJsonNode(String fileName) throws IOException {
        return MAPPER.readTree(Utils.reader(fileName));
    }

here is also a parseJson method from Parser class:

    public <T> T parseJson(String json, Class<T> clazz) throws JsonProcessingException {
        return MAPPER.readValue(json, clazz);
    }

I wrapped it all together in method I wanted (that searches by nickname) and everything works fine, I get desired User object in the end:

    public User getUser(String nickname) throws IOException {
        return Parser.parseJson(Parser.toJsonNode(USERS).get(nickname).toString(), User.class);
    }

And now the test:

    @Test(groups = Group.TEST)
    public void jsonTest() throws IOException, ParseException {
        User user = Utils.getUser("user2");
        log.warn(user.email());
        log.warn(user.password());
    }

output is:

2022-11-23 T 02:47:58.177+0100 [28][WARN] tests.smoke.SmokeTest->jsonTest user2@user.com
2022-11-23 T 02:47:58.177+0100 [28][WARN] tests.smoke.SmokeTest->jsonTest abc123

On the side note I think I will switch to one JSON library in my whole projects, having two of them is unnecessairly causing mess.

Other thing I found important is, when getting to the value behind a key, use asText() method not toString(), as toString() returned text in double quotes! This is actual code fragment from my automation framework:

expected:<"["This field is required"]"> but was:<"[This field is required]">

So it basically put quotes over quotes, that every string in JSON has to be wrapped in anyway...

hetacz
  • 107
  • 10