1

I have come across this question on StackOverflow which asks about converting JSON to Java. The answer shows that another class is modelled to represent the JSON data as well as an object being created and I don't understand why.

Does that object now contain all the information after Gson reads the content or only one key/value pair? If it only contains 1 key/value pair, I'm assuming I would need to create multiple objects for the JSON that I have below which I can the use a loop to iterate over and add the values to a drop down menu?

{
    "1": "Annie",
    "2": "Olaf",
    "3": "Galio",
    "4": "TwistedFate",
    "5": "XinZhao",
    "6": "Urgot",
    "7": "Leblanc",
    "8": "Vladimir",
    "9": "FiddleSticks",
    "10": "Kayle",
    "11": "MasterYi",
    "12": "Alistar",
    "13": "Ryze",
    "14": "Sion",
    "15": "Sivir",
    "16": "Soraka",
    "17": "Teemo",
    "18": "Tristana",
    "19": "Warwick",
    "20": "Nunu"
}

Essentially what I am aiming to do is:

1) Create a list of names with the Values.

2) Sort the list of names (as it comes unsorted) in alphabetical order

3) Loop through the list and add each name to a drop down menu

4) When a name in the drop down menu is selected, the key associated with that value is passed to another url which receives more data.

Sorry if this is unclear. I've spent a couple of hours trying to understand how to get elements from JSON and display it, as well as trying to create a list where I can use the key to display information the name but have had no luck except for using a for-each loop.

Community
  • 1
  • 1
Temptex
  • 87
  • 1
  • 6
  • What, no Sona? Come on! – Shotgun Ninja Jun 02 '15 at 17:36
  • 1
    There's quite a few champs, so I just picked a few out of the list :) – Temptex Jun 02 '15 at 17:41
  • 1
    I guess you're misusing JSON. – Luiggi Mendoza Jun 02 '15 at 17:44
  • In what sense am I miss using JSON? This is how the data is given and I don't really know what I should do with it after it has been parsed. I know how I would like to use it but I don't know how to use it. – Temptex Jun 02 '15 at 17:46
  • 1
    Because what you really have here is an array of strings, so a proper JSON should be: `{ 'names' : [ 'name1', 'name2', 'name3' ... ] }` so you can easily map this to a class `class NameHolder { List names; }`. – Luiggi Mendoza Jun 02 '15 at 17:49
  • This looks just like a `Map`, not a `List` to me. Use `Gson` for you `JSON` needs. You can be done in 5 seconds :) – user489041 Jun 02 '15 at 17:51
  • You should view JSON as an alternative to XML. So you should use it where you would normally use XML - to exchange data between systems (like messaging, remote API) or store structures data (configuration file, database). A JSON is converted to a "native" objects to make it easier to work with. The structure in your question can be simply deserialized into a Map. – vempo Jun 02 '15 at 17:51
  • @LuiggiMendoza This is the code I use to print the JSON data above: JsonElement root = object.get("keys"); System.out.println(root); The "keys" is the top (element?) in the JSON object when its parsed, but the console outputs everything after it. – Temptex Jun 02 '15 at 17:55
  • @user489041I tried to use Map when I called entrySet() with Gson but instead was asked to use a Set. I tried to find a method which would allow me to get a certain string but it wouldn't work. I will go and have an attempt again see what the actual problem is. – Temptex Jun 02 '15 at 17:57
  • Does it have to be Gson? – vempo Jun 02 '15 at 19:30
  • It does not, but I've heard that Gson is the simplest to work with. – Temptex Jun 02 '15 at 19:31
  • Well, not always as my sample with Jackson shows, because using a Jackson feature I was able to sort the data while converting JSON to an object and not afterwards, and then to get an ID by name with the complexity of O(1), without any additional sorting/searching. And all that with very little lines of code. – vempo Jun 02 '15 at 20:05
  • BTW, if you could map names to IDs in the JSON, and not vice versa, the solution would be much easier. But I understand that the structure of JSON is given and cannot be changed, can it? – vempo Jun 02 '15 at 20:17
  • Correct. My Big O notation understanding is really poor but if I understand correctly you can sort and convert in 1 step or? – Temptex Jun 02 '15 at 20:43
  • @Temptex Not only that. You can fetch a key (ID) without looping through the values. String olafId = pairs.get("Olaf"); – vempo Jun 03 '15 at 06:45

2 Answers2

0

Let's use Jackson's feature that allows you to map any property to a single method (you don't really need a getter here I believe). Just swap the key and value in this universal setter, and add to a TreeMap, which is already sorted by key (name). Then you can output the keys (names) in the alphabetical order and get an ID by name easily.

public static void main(String[] args) throws IOException {

    String json = "....."; // your JSON string here

    com.fasterxml.jackson.databind.ObjectMapper mapper = 
        new com.fasterxml.jackson.databind.ObjectMapper();
    ReverseMap pairs = mapper.readValue(json, ReverseMap.class);
    for (Map.Entry<Object, String> entry : pairs.getValues().entrySet()) {
        System.out.println(entry.getKey() + ":" + entry.getValue());
    }
}

public class ReverseMap {

    private TreeMap<Object, String> mapping = new TreeMap<>();

    @com.fasterxml.jackson.annotation.JsonAnySetter
    public void add(String name, Object value) {
        mapping.put(value, name);
    }

    public Map<Object, String> getValues() {
        return mapping;
    }
}
vempo
  • 3,093
  • 1
  • 14
  • 16
  • But... the OP specified using Gson, not Jackson. I prefer Jackson for JSON parsing, but that's just me. – Shotgun Ninja Jun 02 '15 at 19:24
  • 1
    I guess Gson was mentioned just because the linked example uses it, but I may be wrong of course. Ultimately he/she need the job done and my example does it in a few lines of code. I'll ask for clarifications anyway, thanks for pointing out. – vempo Jun 02 '15 at 19:29
-1

Gson Bean Mapping Solution

Okay, what you have is a bit unusual for a JSON object; the keys (the numbers in your case) essentially represent properties of their contained object. That's workable, but you have to understand that, for example, when looking for "Annie" in the JSON object, if you use Gson to map to a "bean" class, which we'll call Data (as in the linked example), then you'd have to create a data object like so:

class Data {
    private String _1;
    // ...
    private String _20;

    public String get1() { return _1; }
    public void set1(String _1) { this._1 = _1; }
    // ...
    public String get20() { return _20; }
    public void set20(String _20) { this._20 = _20; }
}

And by using Data data = new Gson().fromJson(myJsonString, Data.class); on the given string, you'd be able to find "Annie" by calling... uh... data.get1()?

Clearly, this isn't a good solution.

Better Solutions

Since your data doesn't follow the typical format for a JSON object, you have two options:

  1. If you can, refactor your JSON representation to a more verbose, but better representation for parsing.
  2. Use a different approach to parse the existing JSON.

Solution 1: Changing the JSON representation

Refactoring the JSON would result in an object that (preferably) would look like this:

{
    "champions" : [
        {
            "index" : 1,
            "name" : "Annie"
        },
        {
            "index" : 2,
            "name" : "Olaf"
        },
        // ...
    ]
}

This could map easily to a couple of beans that look like this:

class Data {
    private List<Champion> champions;
    // TODO getters and setters
}
class Champion {
    private int index;
    private String name;
    // TODO getters and setters
}

However, this adds a lot of unnecessary clutter to the JSON object, and isn't really necessary with only two fields per champion (the name, and their index).

You could simplify that further like so:

{
    "champions" : [
        "Annie",
        "Olaf",
        // ...
    ]
}

The bean class for that would then be:

class Data {
    private List<String> champions;
    // TODO getters and setters
}

Much simpler, but still requires a change to the JSON you're getting, which in some situations isn't possible. If you used this, though, you could also get rid of the "bean" class entirely, via:

List<String> champions = (List<String>) new Gson().fromJson(myJsonString, new TypeToken<List<String>>(){}.getType());

Solution 2: Changing how the JSON is parsed

The arguably better and cleaner solution is just to change how the JSON is parsed.

The goal here (if I understand you correctly) is to parse the JSON and spit out a collection of strings representing each champion's name, accessible by the numeric index of the champion in the JSON representation.

As such, and because of the way the JSON object is laid out as a simple mapping of strings to strings, we can use Gson to pipe directly into a Map<String, Object>, like so:

Map<String, String> mappedValues = new Gson().fromJson(myJsonString, Map.class);
String anniesName = mappedValues.get("1");  // "Annie"
String olafsName = mappedValues.get("2");   // "Olaf"
boolean hasTwentyOneElements = mappedValues.containsKey("21");   // false

This is shorter, requires no "bean" classes, and keeps the original JSON representation. The downside is that you can't easily tell whether the indices of each entry are correct and consistent; ie. if someone types in the wrong number, or deletes one of the entries.

To get a container of all keys, you just use mappedValues.keySet(), and to get a container of all key-value pairs, you use mappedValues.entrySet(), which gives you a Set<Map.Entry<String, String>>. Both of those can be iterated over, and may be in random order (I'm not sure whether the underlying Map implementation preserves insertion order or not).

To get the index for a given name (ie. champ), you'd use something similar to the following:

String index = null;
for (Map.Entry<String, String> entry : mappedValues.entrySet()) {
    if (champ.equals(entry.getValue())) {
        index = entry.getKey();
        break;
    }
}

Of course, you'd have to check to see if index is null after this, and handle that appropriately, but it's easily doable.

EDIT: @vempo's answer provides a cleaner, more efficient lookup strategy by means of inverting the map (although the answer is written for Jackson, instead of Gson); an adaptation of this for Gson is as follows (and yes, there is a vastly superior version in , left out for sake of availability):

public Map<String, String> invertMap(Map<String, String> input) {
    Map<String, String> newMap = new LinkedTreeMap<String, String>(); // TODO Pick optimal storage class
    for (Map.Entry<String, String> entry : input.entrySet()) {
        newMap.put(entry.getValue(), entry.getKey());
    }
    return newMap;
}

// ...

Map<String, String> mappedValues = invertMap(new Gson().fromJson(myJsonString, Map.class));
String annieIndex = mappedValues.get("Annie");   // "1"
String olafIndex = mappedValues.get("Olaf");     // "2"

It's worth noting that this sacrifices efficiency of constructing the map by effectively building it twice (once by Gson and once more to invert), but it makes value lookup much more efficient.

Community
  • 1
  • 1
Shotgun Ninja
  • 2,540
  • 3
  • 26
  • 32
  • 1
    Two things: 1) Why would I need a bean class? What is its purpose? 2) I would rather not change the JSON and work with what I get as I believe it will make things more complicated for me. I tried to implement the TypeToken earlier and for some reason my Java could not find it and was giving me an error. Is it an external library or could my eclipse just be buggy? I will use what you have given and report back soon. I appreciate what you've done for me! – Temptex Jun 02 '15 at 19:07
  • 1
    @Temptex as I mentioned in comment directly to the question, it seems you're misusing json, it has an odd format to provide the data you want/need. Anyway, looks like you only need the values of the `Map`, which after using `Map mappedValues = new Gson().fromJson(myJsonString, Map.class);` you will only need to use `Collection names = mappedValues.values();` – Luiggi Mendoza Jun 02 '15 at 19:09
  • @LuiggiMendoza Ah, right now I understand what you meant. Right will take that into consideration and make some magic happen. cheers pal! – Temptex Jun 02 '15 at 19:15
  • @Temptex The `TypeToken` class is in the Gson jar, under `com.google.gson.reflect`. Import that and it should work. That being said, I gave multiple different approaches to solving your problem and similar problems; each one has its strengths and weaknesses and could be useful if the data changes in the future. – Shotgun Ninja Jun 02 '15 at 19:17
  • 1
    Just wanted to say @LuiggiMendoza answer works and is the simplest to achieve. But thanks to everyone for helping! – Temptex Jun 02 '15 at 19:26
  • 1
    @ShotgunNinja Marked your answer as accepted because of the multiple approaches given, which hopefully someone will find useful. – Temptex Jun 02 '15 at 19:28
  • This does not address "4) When a name in the drop down menu is selected, the key associated with that value is passed to another url which receives more data." How do you get an ID by name (convert a menu item into its ID for passing to a URL)? – vempo Jun 02 '15 at 20:14
  • @vempo By looping through the `entrySet` of the mapping and comparing against the value, you can find the associated ID, and pass that into whatever generates the URL. I'll add that into my answer. – Shotgun Ninja Jun 02 '15 at 20:16
  • 1
    @ShotgunNinja Come on, that's not efficient! – vempo Jun 02 '15 at 20:19
  • 1
    True, but is this a situation which would benefit more from efficient lookups than it would from cleaner code? ...actually, I take that back, loops like this aren't that clean. – Shotgun Ninja Jun 02 '15 at 20:25