Skip to content

Commit 881bd24

Browse files
committed
Unwrap Jackson2 ArrayNode in JsonNodeValueResolver (jknack#964)
1 parent ef79693 commit 881bd24

File tree

2 files changed

+91
-2
lines changed

2 files changed

+91
-2
lines changed

handlebars-jackson2/src/main/java/com/github/jknack/handlebars/JsonNodeValueResolver.java

+24-2
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@
1717
*/
1818
package com.github.jknack.handlebars;
1919

20+
import java.util.AbstractList;
2021
import java.util.AbstractMap;
2122
import java.util.Collections;
2223
import java.util.Iterator;
2324
import java.util.LinkedHashMap;
2425
import java.util.LinkedHashSet;
26+
import java.util.List;
2527
import java.util.Map;
2628
import java.util.Map.Entry;
2729
import java.util.Set;
@@ -84,7 +86,7 @@ public Object resolve(final Object context) {
8486
* Resolve a {@link JsonNode} object to a primitive value.
8587
*
8688
* @param node A {@link JsonNode} object.
87-
* @return A primitive value, json object, json array or null.
89+
* @return A primitive value, json object as a map, json array as a list, or null.
8890
*/
8991
private static Object resolve(final JsonNode node) {
9092
// binary node
@@ -127,7 +129,12 @@ private static Object resolve(final JsonNode node) {
127129
if (node instanceof ObjectNode) {
128130
return toMap((ObjectNode) node);
129131
}
130-
// container, array or null
132+
// array node to list
133+
if (node instanceof ArrayNode) {
134+
return toList((ArrayNode) node);
135+
}
136+
137+
// container or literal null
131138
return node;
132139
}
133140

@@ -160,6 +167,21 @@ public Set<Map.Entry<String, Object>> entrySet() {
160167
};
161168
}
162169

170+
private static List<Object> toList(final ArrayNode node) {
171+
return new AbstractList<Object>() {
172+
173+
@Override
174+
public Object get(int index) {
175+
return resolve(node.get(index));
176+
}
177+
178+
@Override
179+
public int size() {
180+
return node.size();
181+
}
182+
};
183+
}
184+
163185
@Override
164186
public Set<Entry<String, Object>> propertySet(final Object context) {
165187
if (context instanceof ObjectNode) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.github.jknack.handlebars;
2+
3+
import java.io.IOException;
4+
import java.util.Iterator;
5+
6+
import com.fasterxml.jackson.databind.JsonNode;
7+
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import com.github.jknack.handlebars.context.MapValueResolver;
9+
import com.github.jknack.handlebars.helper.StringHelpers;
10+
11+
import org.junit.Test;
12+
13+
public class Issue964 extends AbstractTest {
14+
15+
@Override
16+
protected Object configureContext(final Object model) {
17+
return Context.newBuilder(model)
18+
.resolver(MapValueResolver.INSTANCE, JsonNodeValueResolver.INSTANCE)
19+
.build();
20+
}
21+
22+
@Test
23+
public void shouldUnwrapJsonArraysAsIterables() throws IOException {
24+
Hash helpers = $("join", StringHelpers.join);
25+
JsonNode tree = new ObjectMapper().readTree("{\"pets\":[\"cat\",\"dog\",\"bird\"]}");
26+
shouldCompileTo("{{join this.pets \", \"}}", tree, helpers, "cat, dog, bird");
27+
}
28+
29+
@Test
30+
public void shouldUnwrapJsonArraysByIndex() throws IOException {
31+
Hash helpers = $("join", StringHelpers.join);
32+
JsonNode tree = new ObjectMapper().readTree("{\"pets\":[\"cat\",\"dog\",\"bird\"]}");
33+
shouldCompileTo("{{join this.pets.[0] this.pets.[1] this.pets.[2] \", \"}}", tree, helpers, "cat, dog, bird");
34+
}
35+
36+
@Test
37+
public void shouldUnwrapJsonArraysRecursively() throws IOException {
38+
Hash helpers = $("elementAt", new ElementAtHelper(), "capitalize", StringHelpers.capitalize);
39+
JsonNode tree = new ObjectMapper().readTree("{\"kidsPets\":[[\"cat\",\"dog\"],[\"bird\",\"mouse\"]]}");
40+
shouldCompileTo("{{capitalize (elementAt (elementAt this.kidsPets 0) 1)}}", tree, helpers, "Dog");
41+
}
42+
43+
private static class ElementAtHelper implements Helper<Iterable<Object>> {
44+
45+
@Override
46+
public Object apply(Iterable<Object> context, Options options) throws IOException {
47+
int targetIndex = options.param(0);
48+
int currentIndex = 0;
49+
50+
Iterator<Object> loop = context.iterator();
51+
52+
while (loop.hasNext()) {
53+
Object it = loop.next();
54+
if (currentIndex++ == targetIndex) {
55+
return it;
56+
}
57+
}
58+
59+
throw new IOException(
60+
"Cannot get element at " + targetIndex + ". " +
61+
"Iterable only has " + currentIndex + " elements."
62+
);
63+
}
64+
65+
}
66+
67+
}

0 commit comments

Comments
 (0)