Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unwrap Jackson2 Array and Object Nodes in JsonNodeValueResolver (#964, #969) #965

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
*/
package com.github.jknack.handlebars;

import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
Expand Down Expand Up @@ -84,7 +86,7 @@ public Object resolve(final Object context) {
* Resolve a {@link JsonNode} object to a primitive value.
*
* @param node A {@link JsonNode} object.
* @return A primitive value, json object, json array or null.
* @return A primitive value, json object as a map, json array as a list, or null.
*/
private static Object resolve(final JsonNode node) {
// binary node
Expand Down Expand Up @@ -127,7 +129,12 @@ private static Object resolve(final JsonNode node) {
if (node instanceof ObjectNode) {
return toMap((ObjectNode) node);
}
// container, array or null
// array node to list
if (node instanceof ArrayNode) {
return toList((ArrayNode) node);
}

// container or literal null
return node;
}

Expand All @@ -153,13 +160,48 @@ public Set<Map.Entry<String, Object>> entrySet() {
Iterator<Map.Entry<String, JsonNode>> it = node.fields();
Set set = new LinkedHashSet();
while (it.hasNext()) {
set.add(it.next());
Map.Entry<String, JsonNode> current = it.next();

set.add(
new Map.Entry<String, Object>() {

@Override
public String getKey() {
return current.getKey();
}

@Override
public Object getValue() {
return resolve(current.getValue());
}

@Override
public Object setValue(Object value) {
throw new UnsupportedOperationException();
}
}
);
}
return set;
}
};
}

private static List<Object> toList(final ArrayNode node) {
return new AbstractList<Object>() {

@Override
public Object get(int index) {
return resolve(node.get(index));
}

@Override
public int size() {
return node.size();
}
};
}

@Override
public Set<Entry<String, Object>> propertySet(final Object context) {
if (context instanceof ObjectNode) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.github.jknack.handlebars;

import java.io.IOException;
import java.util.Iterator;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.jknack.handlebars.context.MapValueResolver;
import com.github.jknack.handlebars.helper.StringHelpers;

import org.junit.Test;

public class Issue964 extends AbstractTest {

@Override
protected Object configureContext(final Object model) {
return Context.newBuilder(model)
.resolver(MapValueResolver.INSTANCE, JsonNodeValueResolver.INSTANCE)
.build();
}

@Test
public void shouldUnwrapJsonArraysAsIterables() throws IOException {
Hash helpers = $("join", StringHelpers.join);
JsonNode tree = new ObjectMapper().readTree("{\"pets\":[\"cat\",\"dog\",\"bird\"]}");
shouldCompileTo("{{join this.pets \", \"}}", tree, helpers, "cat, dog, bird");
}

@Test
public void shouldUnwrapJsonArraysByIndex() throws IOException {
Hash helpers = $("join", StringHelpers.join);
JsonNode tree = new ObjectMapper().readTree("{\"pets\":[\"cat\",\"dog\",\"bird\"]}");
shouldCompileTo("{{join this.pets.[0] this.pets.[1] this.pets.[2] \", \"}}", tree, helpers, "cat, dog, bird");
}

@Test
public void shouldUnwrapJsonArraysRecursively() throws IOException {
Hash helpers = $("elementAt", new ElementAtHelper(), "capitalize", StringHelpers.capitalize);
JsonNode tree = new ObjectMapper().readTree("{\"kidsPets\":[[\"cat\",\"dog\"],[\"bird\",\"mouse\"]]}");
shouldCompileTo("{{capitalize (elementAt (elementAt this.kidsPets 0) 1)}}", tree, helpers, "Dog");
}

private static class ElementAtHelper implements Helper<Iterable<Object>> {

@Override
public Object apply(Iterable<Object> context, Options options) throws IOException {
int targetIndex = options.param(0);
int currentIndex = 0;

Iterator<Object> loop = context.iterator();

while (loop.hasNext()) {
Object it = loop.next();
if (currentIndex++ == targetIndex) {
return it;
}
}

throw new IOException(
"Cannot get element at " + targetIndex + ". " +
"Iterable only has " + currentIndex + " elements."
);
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.github.jknack.handlebars;

import java.io.IOException;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.jknack.handlebars.context.MapValueResolver;
import com.github.jknack.handlebars.helper.StringHelpers;

import org.junit.Test;

public class Issue969 extends AbstractTest {

@Override
protected Object configureContext(final Object model) {
return Context.newBuilder(model)
.resolver(MapValueResolver.INSTANCE, JsonNodeValueResolver.INSTANCE)
.build();
}

@Override
protected Handlebars newHandlebars() {
return super.newHandlebars().with(EscapingStrategy.NOOP);
}

@Test
public void shouldRecursivelyResolveEntries() throws IOException {
Hash helpers = $("join", StringHelpers.join);
JsonNode tree = new ObjectMapper().readTree("{\"pets\":[{\"type\":\"cat\",\"name\":\"alice\"},{\"type\":\"bird\",\"name\":\"bob\"}]}");
shouldCompileTo("{{join this.pets \", \"}}", tree, helpers, "{type=cat, name=alice}, {type=bird, name=bob}");
}

}