【问题标题】:Accessing jsonpath elements with nested objects使用嵌套对象访问 jsonpath 元素
【发布时间】:2019-12-07 21:26:45
【问题描述】:

我希望从数组和对象的 JSON 路径中提取某些值,并将这些值用于进一步处理,并且正在努力访问这些元素。这是 JSON 响应:

[  
 {  
  "od_pair":"7015400:8727100",
  "buckets":[  
     {  
        "bucket":"C00",
        "original":2,
        "available":2
     },
     {  
        "bucket":"A01",
        "original":76,
        "available":0
     },
     {  
        "bucket":"B01",
        "original":672,
        "available":480
     }
    ]
    },
 {  
  "od_pair":"7015400:8814001",
  "buckets":[  
     {  
        "bucket":"C00",
        "original":2,
        "available":2
     },
     {  
        "bucket":"A01",
        "original":40,
        "available":40
     },
     {  
        "bucket":"B01",
        "original":672,
        "available":672
     },
     {  
        "bucket":"B03",
        "original":632,
        "available":632
     },
     {  
        "bucket":"B05",
        "original":558,
        "available":558
     }
    ]
 }
]

我尝试使用 $ 访问根元素,但无法进一步使用它。

这是我写的测试方法。我想提取 od_pair 的值,并且在每个 od_pair 中,我需要能够检索存储桶代码及其可用编号。

public static void updateBuckets(String ServiceName, String DateOfJourney) throws Exception {
    File jsonExample = new File(System.getProperty("user.dir"), "\\LogAvResponse\\LogAvResponse.json");
    JsonPath jsonPath = new JsonPath(jsonExample);

    List<Object> LegList = jsonPath.getList("$");
    // List<HashMap<String, String>> jsonObjectsInArray = jsonPath.getList("$");

    int NoofLegs = LegList.size();
    System.out.println("No of legs :" + NoofLegs);
    for (int j = 0; j <= NoofLegs; j++)
    // for (HashMap<String, String> jsonObject : jsonObjectsInArray) {
    {

        String OD_Pair = jsonPath.param("j", j).getString("[j].od_pair");
        // String OD_Pair = jsonObject.get("od_pair");

        System.out.println("OD Pair: " + OD_Pair);
        List<Object> BucketsList = jsonPath.param("j", j).getList("[j].buckets");

        int NoOfBuckets = BucketsList.size();
        // System.out.println("OD Pair: " + OD_Pair);
        System.out.println("no of Buckets: " + NoOfBuckets);

        for (int i = 0; i < NoOfBuckets; i++) {
            String BucketCode = jsonPath.param("j", j).param("i", i).getString("[j].buckets[i].bucket");
            String Available = jsonPath.param("j", j).param("i", i).getString("[j].buckets[i].available");

            int BucketCodeColumn = XLUtils.getBucketCodeColumn(BucketCode);
            int ServiceRow = XLUtils.getServiceRow(ServiceName, DateOfJourney, OD_Pair);
            System.out.println("Row of " + ServiceName + ":" + DateOfJourney + "is:" + ServiceRow);
            System.out.println("Bucket Code column of " + BucketCode + " is: " + BucketCodeColumn);
            XLUtils.updateAvailability(ServiceRow, BucketCodeColumn, Available);
        }
    }

}

}

这是我看到的错误:

Caused by: 
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup 
failed:
Script1.groovy: 1: unexpected token: [ @ line 1, column 27.
restAssuredJsonRootObject.[j].od_pair

有人可以帮帮我吗?

【问题讨论】:

  • I need to be able to retrieve the bucket codes and their available numbers. 基于od_pair,对吧?
  • 我希望不仅能够访问 od_pair 还能够访问其中的存储桶对象。所以每个 od_pair 都有很多桶作为对象。使用您的 Hashmap 解决方案,我不确定如何访问每个 od_pairs 中的各种存储桶
  • 是的,这是正确的@Fenio
  • 好的,知道了。有一些解决方案,但我会向您展示扩展的和最好的(在我看来)一个。我需要几分钟
  • @Fenio:所以基本上我正在尝试运行嵌套的 for 循环,一个用于获取 od_pair 并在其中一个用于存储桶的 for 循环以获取每个存储桶代码和可用编号。抱歉,因为我是新手,因此我的一些问题在这里可能听起来很幼稚。

标签: rest-assured rest-assured-jsonpath


【解决方案1】:

查看错误消息,您似乎正在使用rest-assured,并且JsonPath 类是来自rest-assured 库的io.restassured.path.json.JsonPath

我相信您知道,但(也许对于其他读者)请注意,这与 Jayway's json-path 不同,并且不是该库中的 com.jayway.jsonpath.JsonPath 类。

另外请注意,正如 documentation 中提到的,rest-assured 使用 Groovy GPath 语法来操作/提取 JSON。

这样,我相信以下内容将提取您需要的内容,即 od_pair 及其对应的具有可用编号的存储桶:

Map<String, Map<String, Integer>> map = JsonPath.with(jsonString).get("collectEntries{entry -> [entry.od_pair, entry.buckets.collectEntries{bucketEntry -> [bucketEntry.bucket, bucketEntry.available]}]}");

对于映射的每个条目,键是 od_pair,值是另一个映射,其键是存储桶,值是可用数字。 jsonString 是您在问题中提供的 JSON。

你可以遍历地图得到你想要的:

for(Map.Entry<String, Map<String, Integer>> entry : map.entrySet())
{
    String od_pair = entry.getKey();

    Map<String, Integer> bucketMap = entry.getValue();

    for(Map.Entry<String, Integer> bucketEntry : bucketMap.entrySet())
    {
        String bucket = bucketEntry.getKey();
        int available = bucketEntry.getValue();
    }
}

打印出你会得到的地图:

{7015400:8727100={C00=2, A01=0, B01=480}, 7015400:8814001={C00=2, A01=40, B01=672, B03=632, B05=558}}

使用 Gson 将地图打印为 JSON:

System.out.println(new GsonBuilder().setPrettyPrinting().create().toJson(map));

你会得到

{
  "7015400:8727100": {
    "C00": 2,
    "A01": 0,
    "B01": 480
  },
  "7015400:8814001": {
    "C00": 2,
    "A01": 40,
    "B01": 672,
    "B03": 632,
    "B05": 558
  }
}

对于背景,字符串collectEntries{entry -&gt; [entry.od_pair, entry.buckets.collectEntries{bucketEntry -&gt; [bucketEntry.bucket, bucketEntry.available]}]}

是一个 Groovy 闭包,它使用来自 Groovy Collections API 的方法:请参阅 CollectionListMap

向@Fenio 大声疾呼上面的纯 Java 解决方案。

【讨论】:

    【解决方案2】:

    OP 让我就如何修复他的代码提出建议,因此是第二个答案。

    我们来分析一下你提供的代码:

    public static void updateBuckets(String ServiceName, String DateOfJourney) throws Exception {
        File jsonExample = new File(System.getProperty("user.dir"), "\\LogAvResponse\\LogAvResponse.json");
        JsonPath jsonPath = new JsonPath(jsonExample);
    
        List<Object> LegList = jsonPath.getList("$");
        // List<HashMap<String, String>> jsonObjectsInArray = jsonPath.getList("$");
    
        int NoofLegs = LegList.size();
        System.out.println("No of legs :" + NoofLegs);
        for (int j = 0; j <= NoofLegs; j++)
        // for (HashMap<String, String> jsonObject : jsonObjectsInArray) {
        {
    
            String OD_Pair = jsonPath.param("j", j).getString("[j].od_pair");
            // String OD_Pair = jsonObject.get("od_pair");
    
            System.out.println("OD Pair: " + OD_Pair);
            List<Object> BucketsList = jsonPath.param("j", j).getList("[j].buckets");
    
            int NoOfBuckets = BucketsList.size();
            // System.out.println("OD Pair: " + OD_Pair);
            System.out.println("no of Buckets: " + NoOfBuckets);
    
            for (int i = 0; i < NoOfBuckets; i++) {
                String BucketCode = jsonPath.param("j", j).param("i", i).getString("[j].buckets[i].bucket");
                String Available = jsonPath.param("j", j).param("i", i).getString("[j].buckets[i].available");
    
                int BucketCodeColumn = XLUtils.getBucketCodeColumn(BucketCode);
                int ServiceRow = XLUtils.getServiceRow(ServiceName, DateOfJourney, OD_Pair);
                System.out.println("Row of " + ServiceName + ":" + DateOfJourney + "is:" + ServiceRow);
                System.out.println("Bucket Code column of " + BucketCode + " is: " + BucketCodeColumn);
                XLUtils.updateAvailability(ServiceRow, BucketCodeColumn, Available);
            }
        }
    
    }
    

    我现在没有使用编译器,所以我可能会错过一些东西。

    #1

    我能看到的第一件事是您将主数组保存到 List&lt;Object&gt; List&lt;Object&gt; LegList = jsonPath.getList("$");

    相反,您可以将其保存为更易于理解的类型,因为 Object 是如此通用,您不知道其中的内容。

    List&lt;HashMap&lt;String, Object&gt;&gt; LegList = jsonPath.getList("$");

    #2 由于评估器,for 循环看起来不正确 j &lt;= NoofLegs;。 这可能会导致IndexArrayOutOfBoundsException 或类似的东西。使用给定的代码,如果您有 4 个分支,for 循环将尝试处理 5 个不正确的分支。

    #3 与 #1 类似,您保存存储桶列表的行 List&lt;Object&gt; BucketsList = jsonPath.param("j", j).getList("[j].buckets"); 也可以改为List&lt;HashMap&lt;String, Object&gt;&gt;

    如果你这样做,你就不需要基于整数的嵌套 for 循环。

    您看,HashMap&lt;String, Object&gt; 实际上对于解析嵌套对象至关重要。字符串只是一个名称,例如 bucketsod_pair。这是 JSON 表示。第二个参数Object 不同。 RestAssured 在 HashMap 中返回不同的类型,这就是我们使用Object 而不是String 的原因。有时它不是字符串。

    基于您的 JSON 的示例:

    收集桶到 HashMaps 列表:

    List&lt;HashMap&lt;String, Object&gt;&gt; bucketsList = jsonPath.param("j", j).getList("[j].buckets");

    列表中的每个 HashMap 都是这样的表示:

    {  
            "bucket":"C00",
            "original":2,
            "available":2
    },
    

    HashMap 中的 Object 在您的情况下是 StringInteger。 因此,如果您从 HashMap 获取元素 bucket,您将获得它的值。

    让我们将它与for 循环结合起来进一步澄清:

    List<HashMap<String, Object>> bucketsList = jsonPath.param("j", j).getList("[j].buckets");
    for (HashMap<String, Object> singleBucket : bucketsList) {
        String firstValue = (String) singleBucket.get("bucket");
        Integer secondValue = (Integer) singleBucket.get("original");
    }
    

    【讨论】:

    • 非常感谢您的解释。您的解释非常清楚,有助于很好地理解这一点。非常感谢您的帮助先生!
    • @Mihir 非常欢迎您 :) 如果您需要帮助,请随时提出更多问题
    • 我喜欢您提供的上述方法。我对此只有一个问题。我在下面再次添加了一个部分。如果你能回复。@Fenio
    【解决方案3】:

    我建议将您的 JSON 解析为 Java 类以简化处理。

    如何做到这一点? 首先,我们需要创建代表您提供的 JSON 的 Java 类。

    让我们分析一下 JSON。 从数组开始。该数组包含多个 JSON 对象。这些对象包含od_pair 值和称为buckets 的对象数组。

    让我们创建一个类(你可以随意命名)Pair

    public class Pair {
        public String od_pair; //this name is important because it corresponds with the json element's name!
        public List<BucketObject> buckets; //same here!
    }
    

    此类表示主数组中的单个 JSON 对象。它包含 od_pair 值和嵌套 JSON 数组,但采用 Java 表示 -> BucketObject 类列表。让我们创建BucketObject 类:

    public class BucketObject { //this name is NOT importnat
        public String bucket; //names are important
        public int original;
        public int available;
    }
    

    每个对象中只有 3 个值。

    现在,是时候将 JSON 解析为书面类了。

    JsonPath path = JsonPath.from(json);
    Pair[] pairs = path.getObject("$", Pair[].class);
    

    请记住,Pair 是单个 JSON 对象。这就是为什么我们从美元符号$ 表示的根开始解析,并声明应该将JSON 解析为Pair 对象的ARRAY!

    现在,处理会简单得多!

    我不确定您需要什么,但我将向您展示如何根据 od_pair 字段从存储桶中获取数据的示例,您应该能够弄清楚其余的处理过程。

    所以,我们有 Pair 类的数组:Pair[] pairs;

    现在,我们要根据 od_pair 值获取 1 个 Pair 对象。

        public static Pair getPairBasedOnOdPairValue(Pair[] pairs, String odPairValue) {
            for (Pair pair : pairs) {
                if (pair.od_pair.equals(odPairValue)) return pair;
            }
    
            throw new NoSuchElementException();
        }
    

    现在,我们有了Pair 对象。我们可以使用此对象访问buckets

    List<BucketObject> buckets = pair.buckets;
    

    其余的处理过程是迭代List&lt;BucketObject&gt; 并获得所需的值。

    希望对你有帮助!

    【讨论】:

    • 非常感谢@Fenio,我将实施这种方法并更新它的进展情况。
    • @Mihir 如果您有更多问题 - 请创建另一个问题。我会定期扫描标记为 rest assured 的问题 :)
    • 好的,肯定会@Fenio。非常感谢
    • 非常感谢您的解决方案。只是为了我的理解和学习,您是否也可以对我之前编写的for循环代码提出建议。我在哪里嵌套了一个用于获取 od_pair 和一个用于获取存储桶信息的循环?我在那里错过了什么或者我哪里出错了? @Fenio
    • @Mihir 我明天去看看