3

I had to process some json, which could come in slightly different formats (and where I only needed a subset of the json data) and I used JsonPointer (from Jackson) to query the json. I wrote a non-functional solution to the problem, which worked for me, but I wanted to try a functional approach for learning purposes. In the below test program you can see my two solutions. They both work, but the functional solution became quite verbose and I have an annoying warning from Intellij regarding use of get() without isPresent check. I would like to see proposals how to improve the functional implementation and I am happy to see solutions using third-party libraries. The basic problem here, I suppose, is how to model an if-else-if-else, where each branch should return some value, in a functional way.

@Test
public void testIt() {
    ObjectMapper om = new ObjectMapper();
    ImmutableList.of(
            "{ \"foo\": { \"key\": \"1\" } }",
            "{ \"bar\": { \"key\": \"1\" } }",
            "{ \"key\": \"1\" }")
            .forEach(str -> {
                try {
                    System.out.println("Non-functional: " + getNode(om.readTree(str)));
                    System.out.println("Functional: " + getNodeFunc(om.readTree(str)));
                } catch (Exception e) {
                    throw new RuntimeException("", e);
                }
            });
}

private JsonNode getNode(JsonNode parentNode) {
    JsonPointer jp1 = JsonPointer.compile("/foo");
    JsonPointer jp2 = JsonPointer.compile("/bar");
    if (!parentNode.at(jp1).isMissingNode()) {
        return parentNode.at(jp1);
    } else if (!parentNode.at(jp2).isMissingNode()) {
        return parentNode.at(jp2);
    }
    return parentNode;
}

private JsonNode getNodeFunc(JsonNode parentNode) {
    BiFunction<JsonNode, String, Optional<JsonNode>> findNode = (node, path) -> {
        JsonPointer jp = JsonPointer.compile(path);
        return node.at(jp).isMissingNode() ? Optional.empty() : Optional.of(node.at(jp));
    };

    return findNode.apply(parentNode, "/foo")
            .map(Optional::of)
            .orElseGet(() -> findNode.apply(parentNode, "/bar"))
            .map(Optional::of)
            .orElse(Optional.of(parentNode))
            .get(); // Intellij complains here: Optional.get() without isPresent check
}
8
  • 1
    What's wrong with the non-functional code? Looks perfectly fine to me. Commented Jan 22, 2019 at 20:07
  • 1
    the thing I feel I learn again and again when trying to write code in a functional style in Java is how much uglier (most) functional code is in Java ;) Commented Jan 22, 2019 at 20:10
  • 1
    Why are you using map(Optional::of)? findNode.apply() returns an Optional<JsonNode> so you don't need to use map(). Commented Jan 22, 2019 at 20:11
  • 2
    @AndyTurner I felt that way at first too, but after using it with groovy for quite a while and now java, NOT being able to write functionally seems really awkward and restrictive. Hard to explain until you've done it for a few years and made some new pathways in your brain... Exceptionally frustrating is that things like if(), try/catch and switch don't return a value leading to ugly syntax like defining a variable to null before setting it in a switch or try/catch. if() uses the ?: as a functional equivalent, but the syntax is unique and awkward if you split lines. Commented Jan 22, 2019 at 21:13
  • 2
    @AndyTurner Try looking at it from a higher level--it reads like a book. Filter it (get rid of ones that don't exist), find the first one, return the parentNode.at or the parentNode if it doesn't exist. To me that's amazingly easy to read, harder to compose perhaps. Compare that to the question and see which is more readable. I highly recommend embracing rather than fighting :) Commented Jan 22, 2019 at 21:24

2 Answers 2

7

I would rewrite it to

private JsonNode getNodeFunc2(JsonNode parentNode) {
    return Stream.of(JsonPointer.compile("/foo"), JsonPointer.compile("/bar"))
                 .filter(i -> !parentNode.at(i).isMissingNode())
                 .findFirst()
                 .map(parentNode::at)
                 .orElse(parentNode);
}

or

private JsonNode getNodeFunc3(JsonNode parentNode) {
    return Stream.of(JsonPointer.compile("/foo"), JsonPointer.compile("/bar"))
                 .map(parentNode::at)
                 .filter(Predicate.not(JsonNode::isMissingNode))
                 .findFirst()
                 .orElse(parentNode);
}

or

private JsonNode getNodeFunc4(JsonNode parentNode) {
    return Stream.of("/foo", "/bar")
                 .map(JsonPointer::compile)
                 .map(parentNode::at)
                 .filter(Predicate.not(JsonNode::isMissingNode))
                 .findFirst()
                 .orElse(parentNode);
}

because the piece

if (!parentNode.at(jp1).isMissingNode()) {
    return parentNode.at(jp1);
} else if (!parentNode.at(jp2).isMissingNode()) {
    return parentNode.at(jp2);
}

is code duplication and can be neatly handled by a loop:

for (JsonPointer jsonPointer : jsonPointers) {
    JsonNode kid = parentNode.at(jsonPointer);
    if (!kid.isMissingNode()) {
         return kid;
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Predicate.not() is Java 11? Unfortunately, we're still on Java 8 in the project where I work, but looking to upgrade in near future. Very helpful answer, I learnt from it, which was my objective with this question, thanks!
-2

Just something to think about, the getNode function is perfect functional code:

  • The output depends only on its inputs parameters and its internal algorithm.
  • It has no side effects (It doesn't read anything from the outside world or write anything to the outside world)
  • It will always return the same output from the same input.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.