Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Controlling the type of data returned by JsonPath's read() method

Tags:

java

jsonpath

I want JsonPath to always return the data it parses as a string. However, the return type of JsonPath's read() method varies depending on the the type of the data being parsed, because JsonPath always makes a guess at what the data type is and returns results of that type.

The problem is I have no way of knowing what kind of data is going to be returned as I only have access to a set of paths I need to feed into the read() function. This means that I have to store the returned JsonPath data in an array of Objects and use a pretty ugly collection of if/else-if statements involving the instanceof keyword to figure out what the data is (and then cast it to the appropriate type).

Does anyone know a way to force JsonPath to return a string? Or can another think of a better work around?

Here's what I'm doing right now (in Java):

ArrayList<Object> realData = JsonPath.read(dataDumpFile, current.getPath());

What I'd like to do:

ArrayList<String> realData = JsonPath.read(dataDumpFile, current.getPath());

In the docs I found this:

When using JsonPath in java its important to know what type you expect in your result. JsonPath will automatically try to cast the result to the type expected by the invoker.

//Will throw an java.lang.ClassCastException    
List<String> list = JsonPath.parse(json).read("$.store.book[0].author")

//Works fine
String author = JsonPath.parse(json).read("$.store.book[0].author")

What does "the type expected by the invoker" mean?

like image 795
Adam Avatar asked Nov 22 '16 17:11

Adam


2 Answers

In a typical use case of JsonPath, the library assumes we know what to expect from a path:

  • Is the path definite? A path is:

    • definite if there can only be one result, e.g. $..book[0] for the first book.
    • indefinite if multiple results are possible, e.g. $..book[?(@.isbn)] for all books with certain properties; the return type can be a list.
  • What specific type should the result be? For example, you can expect return types Date or List<Date> instead of Object or List<Object>.

In your case, all of the above doesn't matter because you don't know the type beforehand, you just need the results as JSON strings.

The default parser in JsonPath is too smart and will convert everything to nice LinkedHashMap objects; but if you use JacksonJsonNodeJsonProvider, you'll get results as JsonNode objects, and you're one toString() call away from JSON string results.

Even better, you can use the JsonPath option ALWAYS_RETURN_LIST, which means you don't need to worry about whether your path is definite or indefinite (whether your result is a single object or a list).

// Example from https://github.com/jayway/JsonPath#path-examples
final String json = "{\"store\": {\"book\": [{\"category\": \"reference\",\"author\": \"Nigel Rees\",\"title\": \"Sayings of the Century\",\"price\": 8.95},{\"category\": \"fiction\",\"author\": \"Evelyn Waugh\",\"title\": \"Sword of Honour\",\"price\": 12.99},{\"category\": \"fiction\",\"author\": \"Herman Melville\",\"title\": \"Moby Dick\",\"isbn\": \"0-553-21311-3\",\"price\": 8.99},{\"category\": \"fiction\",\"author\": \"J. R. R. Tolkien\",\"title\": \"The Lord of the Rings\",\"isbn\": \"0-395-19395-8\",\"price\": 22.99}],\"bicycle\": {\"color\": \"red\",\"price\": 19.95}},\"expensive\": 10}";

Configuration conf = Configuration.builder().jsonProvider(new JacksonJsonNodeJsonProvider())
        .options(Option.ALWAYS_RETURN_LIST, Option.SUPPRESS_EXCEPTIONS).build();

ArrayNode node = JsonPath.using(conf).parse(json).read("$.store.book[*]"); // indefinite
for (Object o : node) {
    System.out.println(o.toString());
}
// {"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95}
// {"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99}
// {"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}
// {"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":22.99}

node = JsonPath.using(conf).parse(json).read("$.store.book[0].author"); // definite
for (Object o : node) {
    System.out.println(o.toString());
}
// "Nigel Rees"
like image 110
approxiblue Avatar answered Sep 28 '22 14:09

approxiblue


Let assume this is your JSON:

{
    "store": {
        "book": [
            {
                "category": "reference",
                "author": "Nigel Rees",
                "title": "Sayings of the Century",
                "price": 8.95
            },
            {
                "category": "fiction",
                "author": "Evelyn Waugh",
                "title": "Sword of Honour",
                "price": 12.99
            },
            {
                "category": "fiction",
                "author": "Herman Melville",
                "title": "Moby Dick",
                "isbn": "0-553-21311-3",
                "price": 8.99
            },
            {
                "category": "fiction",
                "author": "J. R. R. Tolkien",
                "title": "The Lord of the Rings",
                "isbn": "0-395-19395-8",
                "price": 22.99
            }
        ],
        "bicycle": {
            "color": "red",
            "price": 19.95
        }
    },
    "expensive": 10
}

Instead of parsing the whole JSON you can query that and get only those string values (as paths), then you can easily loop through them and get what you want:

Configuration conf = Configuration.builder()
     .options(Option.AS_PATH_LIST).build();

List<String> authorPathList = using(conf).parse(json).read("$..author");

This will return the list of authors paths:

["$['store']['book'][0]['author']",
"$['store']['book'][1]['author']",
"$['store']['book'][2]['author']",
"$['store']['book'][3]['author']"]

Now you can loop through them and get your value:

for (string authorPath : authorPathList) {
    String author = JsonPath.parse(json).read(authorPath);
}
like image 36
Yaser Avatar answered Sep 28 '22 15:09

Yaser