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



我在StackOverflow上遇到了这个问题,它询问如何将JSON转换为Java。答案表明,另一个类被建模来表示JSON数据以及正在创建的对象,我不明白为什么。

该对象现在包含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)用Values创建一个名字列表。

2)按字母顺序对名字列表进行排序(当它未排序时)

3)遍历列表并将每个名称添加到下拉菜单

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

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

让我们使用Jackson的特性,它允许您将任何属性映射到单个方法(我相信这里并不需要getter)。只需交换这个通用setter中的键和值,并添加到已经按键(名称)排序的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;
    }
}

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);,您可以通过调用…来找到"Annie"。嗯…data.get1() ?

显然,这不是一个好的解决方案。<<p> 更好解决方案/strong>

由于您的数据不遵循JSON对象的典型格式,因此您有两个选项:

  1. 如果可以,重构你的JSON表示为一个更详细,但更好的解析表示。
  2. 使用不同的方法来解析现有的JSON

解决方案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对象增加很多不必要的混乱,并且对于每个champion只有两个字段(名称和索引)来说,这并不是真正必要的。

你可以进一步简化如下:

{
    "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表示。缺点是你不能很容易地判断每个条目的索引是否正确和一致;ie。如果有人输入了错误的数字,或者删除了其中一个条目。

要获得包含所有键的容器,只需使用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在此之后是否为空,并适当地处理,但这很容易做到。

EDIT: @vempo的答案通过倒转映射提供了一个更干净、更有效的查找策略(尽管答案是为Jackson而不是Gson编写的);对Gson的修改如下(是的,java-8中有一个非常优秀的版本,为了可用性省略了):

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构建,另一次用于反转)来牺牲构造映射的效率,但它使值查找更加高效。

最新更新