2

There's JSON with some product data, as an example:

{
  "sku": 123,
  "product": {
    "name": "Some name",
    "images": {
      "normalImage": "http://somelink.com/1.jpg",
      "bigImage": "http://somelink.com/1b.jpg"
    }
  }
}

I want to pick the image link, but bigImage exists only in some products, so sometimes I need to pick normalImage instead.

The obvious solution looks like:

jmespath.search('product.images.bigImage') or jmespath.search('product.images.normalImage')

but I feel it could be done better. How to do it in an optimal way using JMESPath syntax?

sortas
  • 1,527
  • 3
  • 20
  • 29

2 Answers2

2

How about the following for using only JMESPath syntax?

product.images.[bigImage, normalImage][?@]|[0]

The idea being that we make an array of all the images we want to use in order of preference, filter out the ones that are missing, then pick the first item in the remaining array.

Caveat - this doesn't distinguish between missing and null (or other "falsey" values, such as empty strings) so you might need to tweak it a bit if that matters to you for your particular case.

Weeble
  • 17,058
  • 3
  • 60
  • 75
1

You could create a CustomFunctions class to do this, similar to the examples given in the GitHub page.

from jmespath import search
from jmespath import functions
from jmespath import Options

from json import loads

class CustomFunctions(functions.Functions):

    # Method that selects 'bigImage' key value if it exists
    # Otherwise return 'normalImage' value
    # dict.get() is perfect for this, since it returns a default value if a key doesn't exist
    # Use type 'object' since thats the equivalant type to a Python dictionary in JSON
    # Make sure to decorate function signature as well to indicate types
    # Make sure to also put _func_ before your function name
    @functions.signature({'types': ['object']})
    def _func_choose_key(self, d):
        return d.get('bigImage', d['normalImage'])

if __name__ == "__main__":

    # Get custom function options
    options = Options(custom_functions=CustomFunctions())

    # Test method which runs JMESPath query with custom function
    def test(json):
        json_dict = loads(json)
        return search('product.images | choose_key(@)', json_dict, options=options)


    # TEST 1 - bigImage key exists
    json1 = """{
        "sku": 123,
        "product": {
            "name": "Some name",
            "images": {
                "normalImage": "http://somelink.com/1.jpg",
                "bigImage": "http://somelink.com/1b.jpg"
            }
        }
    }"""

    print("Test1: %s" % test(json1))


    # TEST 2 - bigImage key doesn't exist
    json2 = """{
        "sku": 123,
        "product": {
            "name": "Some name",
            "images": {
                "normalImage": "http://somelink.com/1.jpg"
            }
        }
    }"""


    print("Test2: %s" % test(json2))

Which prints out the following results:

Test1: http://somelink.com/1b.jpg  # When bigImage key exists
Test2: http://somelink.com/1.jpg   # When bigImage key doesn't exist

If JMESPath gets too complicated, we can always use the good old standard dictionary approach:

def test2(json):
    json_dict = loads(json)
    images = json_dict["product"]["images"]
    return images.get("bigImage", images["normalImage"])

# TEST 1 - bigImage key exists
json1 = """{
    "sku": 123,
    "product": {
        "name": "Some name",
        "images": {
            "normalImage": "http://somelink.com/1.jpg",
            "bigImage": "http://somelink.com/1b.jpg"
        }
    }
}"""

print("Test1: %s" % test2(json1))


# TEST 2 - bigImage key doesn't exist
json2 = """{
    "sku": 123,
    "product": {
        "name": "Some name",
        "images": {
            "normalImage": "http://somelink.com/1.jpg"
        }
    }
}"""


print("Test2: %s" % test2(json2))

Which also prints the same results:

Test1: http://somelink.com/1b.jpg  # When bigImage key exists
Test2: http://somelink.com/1.jpg   # When bigImage key doesn't exist
RoadRunner
  • 25,803
  • 6
  • 42
  • 75