建模类来表示JSON数据的原因是什么?我需要做什么?

时间:2015-06-02 17:35:20

标签: java json gson

我在StackOverflow上遇到过这个问题,询问converting JSON to Java.答案显示另一个类被建模来表示JSON数据以及正在创建的对象,我不明白为什么。< / p>

该对象现在是否包含Gson读取内容或仅一个键/值对后的所有信息?如果它只包含1个键/值对,我假设我需要为我下面的JSON创建多个对象,我可以使用循环迭代并将值添加到下拉菜单中?

{
    "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"
}

基本上我的目标是:

1)使用值创建名称列表。

2)按字母顺序对名称列表(因为它未分类)排序

3)循环浏览列表并将每个名称添加到下拉菜单

4)当选择下拉菜单中的名称时,与该值相关联的键将传递给另一个接收更多数据的URL。

对不起,如果不清楚的话。我花了几个小时试图了解如何从JSON获取元素并显示它,以及尝试创建一个列表,我可以使用该键来显示信息名称但除了使用之外没有运气一个for-each循环。

2 个答案:

答案 0 :(得分:0)

让我们使用杰克逊的功能,允许您将任何属性映射到单个方法(我相信你真的不需要吸气剂)。只需交换此通用设置器中的键和值,然后添加到已按键(名称)排序的TreeMap。然后,您可以按字母顺序输出键(名称),并轻松地按名称获取ID。

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;
    }
}

答案 1 :(得分:-1)

Gson Bean制图解决方案

好的,你拥有的JSON对象有点不寻常;键(在您的情况下为数字)基本上表示其包含对象的属性。这是可行的,但你必须明白,例如,当在JSON对象中寻找“Annie”时,如果你使用Gson映射到“bean”类,我们称之为Data(如链接的例子),然后你必须像这样创建一个数据对象:

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; }
}

通过在给定字符串上使用Data data = new Gson().fromJson(myJsonString, Data.class);,您可以通过调用......嗯... data.get1()找到“Annie”?

显然,这不是良好的解决方案。

更好的解决方案

由于您的数据不符合JSON对象的典型格式,因此您有两种选择:

  1. 如果可以,请将您的JSON表示重构为更详细但更好的解析表示。
  2. 使用不同的方法来解析现有的JSON。
  3. 解决方案1:更改JSON表示

    重构JSON会导致一个对象(最好)看起来像这样:

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

    这可以很容易地映射到几个看起来像这样的bean:

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

    然而,这给JSON对象增加了许多不必要的混乱,并且每个冠军只有两个字段(名称及其索引)并不是必需的。

    您可以进一步简化:

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

    那个bean类将是:

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

    更简单,但仍需要更改您获得的JSON,这在某些情况下是不可能的。但是,如果你使用了这个,你也可以完全摆脱“bean”类,通过:

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

    解决方案2:更改解析JSON的方式

    可以说更好更清洁的解决方案就是改变JSON的解析方式。

    这里的目标(如果我理解正确的话)是解析JSON并吐出代表每个冠军名字的字符串集合,可以通过JSON表示中的冠军的数字索引访问。

    因此,由于JSON对象的布局方式是字符串到字符串的简单映射,我们可以使用Gson直接管道输入Map<String, Object>,如下所示:

    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
    

    这个更短,不需要“bean”类,并保留原始的JSON表示。缺点是您无法轻易判断每个条目的索引是否正确和一致;即。如果有人输入错误的号码,或删除其中一个条目。

    要获取所有密钥的容器,只需使用mappedValues.keySet(),并获取所有键值对的容器,即可使用mappedValues.entrySet(),它会为您提供Set<Map.Entry<String, String>>。这两个都可以迭代,并且可能是随机顺序(我不确定基础Map实现是否保留了插入顺序。)

    要获取给定名称的索引(即champ),您将使用类似于以下内容的内容:

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

    当然,在此之后你必须检查index是否为空,并妥善处理,但它很容易实现。

    编辑: @vempo's answer通过反转地图提供更清晰,更有效的查找策略(虽然答案是为杰克逊而不是Gson编写的);对Gson的改编如下(是的,在中有一个非常优越的版本,为了可用性而遗漏了):

    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"
    

    值得注意的是,这会牺牲通过有效地构建地图两次来构建地图的效率(一次由Gson构建,再一次反转),但它使得值查找更有效率。