parse recursive nested field using jackson
Asked Answered
F

1

0

I have a below JSON and from which I need to make a Map<String, String>. In the below JSON, I have only three levels of parentCategory but in general it can be more or sometimes it can be less.

  • Key of my map will be 79720 which is id of categories section and value should be 10987 which is id of last parentCategory.

Now I can have nested parentCategory inside them so I am having issues making a POJO for this. I can have one level parentCategory or I can have nested parentCategory inside each parentCategory and I always need to get id for the last parentCategory.

So I am creating a POJO for this by going to jsonschema2pojo and provided my JSON where it generated me all the files that I need. With the below JSON, it has created three classes like this ParentCategory, ParentCategory_ and ParentCategory__ for parentCategory field. Now since I don't know how many parentCategory levels I have so I am not sure what is the best way to generate a POJO for them and extract id field for last parentCategory. Is there any way to do this using Jackson?

I have to read all this data in POJO as I am also reading all other fields as well.

{
    "paginationResponse": {
        "pageNumber": 1,
        "entriesPerPage": 200,
        "totalPages": 3
    },
    "listings": [
        {
            "categories": [
                {
                    "id": "79720",
                    "name": "Sunglasses",
                    "localizedName": "Sunglasses",
                    "level": 4,
                    "leafCategory": true,
                    "parentCategory": {
                        "id": "394",
                        "name": "Sunglasses & Fashion Eyewear",
                        "localizedName": "Sunglasses & Fashion Eyewear",
                        "level": 3,
                        "leafCategory": false,
                        "parentCategory": {
                            "id": "2340",
                            "name": "Men's Accessories",
                            "localizedName": "Men's Accessories",
                            "level": 2,
                            "leafCategory": false,
                            "parentCategory": {
                                "id": "10987",
                                "name": "Clothing, Shoes & Accessories",
                                "localizedName": "Clothing, Shoes & Accessories",
                                "level": 1,
                                "leafCategory": false
                            }
                        }
                    }
                }
            ],
            "processlisting": {
                ....
            },
            "processlistingmetainfo": {
                ...
            },
            "processlistingproperties": [
                {
                    "propertyName": "item_url",
                    "propertyValues": [
                        {
                            "stringValue": "url"
                        }
                    ]
                },
                {
                    "propertyName": "listing_site_id",
                    "propertyValues": [
                        {
                            "stringValue": "0"
                        }
                    ]
                }
            ]
        }
    ],
    "total": 100
}

Note: I don't have control over this JSON structure as it's not owned by us so can't change JSON structure at all.

Below are the three classes for ParentCategory which was generated basis on above JSON but in general I can have multiple levels of ParentCategory and I don't know beforehand.

public class ParentCategory {
    @JsonProperty("id")
    private String id;
    @JsonProperty("name")
    private String name;
    @JsonProperty("localizedName")
    private String localizedName;
    @JsonProperty("level")
    private long level;
    @JsonProperty("leafCategory")
    private boolean leafCategory;
    @JsonProperty("parentCategory")
    private ParentCategory_ parentCategory;
    @JsonIgnore
    private Map<String, Object> additionalProperties = new HashMap<String, Object>();

    ....

}

public class ParentCategory_ {
    @JsonProperty("id")
    private String id;
    @JsonProperty("name")
    private String name;
    @JsonProperty("localizedName")
    private String localizedName;
    @JsonProperty("level")
    private long level;
    @JsonProperty("leafCategory")
    private boolean leafCategory;
    @JsonProperty("parentCategory")
    private ParentCategory__ parentCategory;
    @JsonIgnore
    private Map<String, Object> additionalProperties = new HashMap<String, Object>();

    ...

}


public class ParentCategory__ {
    @JsonProperty("id")
    private String id;
    @JsonProperty("name")
    private String name;
    @JsonProperty("localizedName")
    private String localizedName;
    @JsonProperty("level")
    private long level;
    @JsonProperty("leafCategory")
    private boolean leafCategory;
    @JsonIgnore
    private Map<String, Object> additionalProperties = new HashMap<String, Object>();

    ...

}
Fijian answered 24/1, 2018 at 23:20 Comment(1)
Looks to me that you need a better JSON mapping. Instead of hardcoding identical recursive objects, create a tree-like mapping where each level represented with the same class with optional next parent. That way you can work with your tree as with normal tree, using recursive traversals and transformations.Haik
T
1

Generally speaking - The best tool for the task of retrieving particular JSON properties is Json-Path which provides a rich query language to search a JSON tree.

Regarding the question, it is requried to retrieve two unrelated properties, so two scans of the JSON tree are required. The question does not specify how the input is read, so perhaps it is possible to read the whole input stream into one String.

Below is the code with the queries needed to retrieve both properties. This will work regardless of the number of parentCategory levels. The trick is that the last object is the only one that does not have a child parentCategory.

I have added comments to the code that explain the query text

    String categoryIdJsonPath = 
        "$" +              // start from tree root
        ".listings[0]" +   // get listings array's first (only) object
        ".categories[0]" + // get categories array's first (only) object
        ".id";             // get id property
    String lastParentIdJsonPath = 
        "$" +                         // start from tree root
        ".listings[0]" +              // get listings array's first (only) object
        ".categories[0]" +            // get categories array's first (only) object
        "..parentCategory" +          // do deep scan for all nested parentCategory objects 
        "[?(!(@.parentCategory))]" +  // filter by the object that does NOT have parentCategory property
        ".id";                        // get id property
    try {
        // read the whole input so it can be scanned twice
        String jsonInput = new String(Files.readAllBytes(Paths.get("C://temp/test.json")), Charset.forName("UTF-8"));
        String categoryId = JsonPath.read(jsonInput, categoryIdJsonPath);
        System.out.println(categoryId);
        // return type is always List when deep scan is requested
        List<String> lastParent = JsonPath.read(jsonInput, lastParentIdJsonPath);
        System.out.println(lastParent.get(0));
    } catch (Exception e) {
        e.printStackTrace();
    }
Trichite answered 25/1, 2018 at 14:24 Comment(5)
I am making a HTTP call and reading all JSON data as a POJO and then I am using that POJO to extract everything. So I was thinking is there any way to read this as a POJO and then extract last id of parentCategory? I can easily extract 79720 which is id of category but my POJO is not working at all to extract last id of parentCategory.Fijian
but why read all data into POJO when all you need are two properties? read the HTTP response into String and use the solution I gave.Trichite
In real I am reading lot of fields not just those two so that's why I created a POJO. And I got stuck when I needed to extract last id of parentCategoryFijian
then you should mention this in the question. this answer is the best fit for what is asked here. if you need to read lots of fields, then read the json into a Map (jackson can do this) and then query the map entriesTrichite
I found one bug in your suggestion while I was testing out. In my case listings is an array so it can have multiple listing json object not just one. In the question I just gave an example to make it simple otherwise question would have become big. So that means for each listing object, I will have different categoryId and different last partentCategory id. Can you help me on how to resolve this issue? I already have a List<Listing> object here.Fijian

© 2022 - 2024 — McMap. All rights reserved.