Skip to content

Commit

Permalink
Jackson utility for serializing to/from map (which can represent a qu…
Browse files Browse the repository at this point in the history
…ery string)
  • Loading branch information
jjlauer committed Sep 25, 2024
1 parent 2432220 commit b9b8203
Show file tree
Hide file tree
Showing 2 changed files with 221 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.fizzed.crux.jackson;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

public class QueryParamMapper {

static public Map<String,String> toQueryParams(ObjectMapper objectMapper, Object value) throws IOException {

final JsonNode node = objectMapper.valueToTree(value);

// node MUST be an object
if (!node.isObject()) {
throw new IOException("Only objects are supported");
}

// build a Map<String,String> ourselves of this object
final Map<String,String> params = new LinkedHashMap<>();

Iterator<Map.Entry<String,JsonNode>> fieldIterator = node.fields();

while (fieldIterator.hasNext()) {
Map.Entry<String,JsonNode> field = fieldIterator.next();
String fieldName = field.getKey();
JsonNode fieldNode = field.getValue();

if (fieldNode.isValueNode()) {
params.put(fieldName, fieldNode.asText());
} else if (fieldNode.isArray()) {
ArrayNode arrayNode = (ArrayNode)fieldNode;
Iterator<JsonNode> arrayIterator = arrayNode.elements();
StringBuilder sb = new StringBuilder();

while (arrayIterator.hasNext()) {
JsonNode arrayFieldNode = arrayIterator.next();

if (arrayFieldNode.isValueNode()) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(arrayFieldNode.asText());
} else {
// we can only serialize arrays 1-level deep!
throw new IOException("Only objects that contain 1-level deep of iterables are supported");
}
}

params.put(fieldName, sb.toString());
}
}

return params;
}

static public <T> T fromQueryParams(ObjectMapper objectMapper, Map<String,String> params, Class<T> type) throws IOException {

// build a map of key -> values, where comma-delimited values are turned into lists
final Map<String,Object> data = new HashMap<>();

for (Map.Entry<String,String> entry : params.entrySet()) {
final String key = entry.getKey();
final String value = entry.getValue();

if (value != null && value.contains(",")) {
String[] tokens = value.split(",");
data.put(key, tokens);
} else {
data.put(key, value);
}
}

return objectMapper.convertValue(data, type);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package com.fizzed.crux.jackson;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;

import java.util.*;

import static java.util.Arrays.asList;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;

public class QueryParamMapperTest {

public enum Animal {
DOG,
CAT,
SHEEP
}

static public class A {

private Integer id;
private String name;
private List<String> types;
private List<Long> limits;
private Set<Animal> animals;

public Integer getId() {
return id;
}

public A setId(Integer id) {
this.id = id;
return this;
}

public String getName() {
return name;
}

public A setName(String name) {
this.name = name;
return this;
}

public List<String> getTypes() {
return types;
}

public A setTypes(List<String> types) {
this.types = types;
return this;
}

public List<Long> getLimits() {
return limits;
}

public A setLimits(List<Long> limits) {
this.limits = limits;
return this;
}

public Set<Animal> getAnimals() {
return animals;
}

public A setAnimals(Set<Animal> animals) {
this.animals = animals;
return this;
}
}

@Test
public void toQueryParams() throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();

A a = new A();
a.setId(1);
a.setName("Wow");
a.setTypes(asList("A", "B", "C"));
a.setLimits(asList(1L, 2L, 3L));
a.setAnimals(new LinkedHashSet<>(asList(Animal.DOG, Animal.CAT)));

Map<String,String> values = QueryParamMapper.toQueryParams(objectMapper, a);

assertThat(values, hasEntry("id", "1"));
assertThat(values, hasEntry("name", "Wow"));
assertThat(values, hasEntry("types", "A,B,C"));
assertThat(values, hasEntry("limits", "1,2,3"));
assertThat(values, hasEntry("animals", "DOG,CAT"));
}

@Test
public void fromQueryParams() throws Exception {
final ObjectMapper objectMapper = new ObjectMapper();

Map<String,String> values = new HashMap<>();
values.put("id", "1");

final A a1 = QueryParamMapper.fromQueryParams(objectMapper, values, A.class);

assertThat(a1.getId(), is(1));

values.put("name", "Hello!");

final A a2 = QueryParamMapper.fromQueryParams(objectMapper, values, A.class);

assertThat(a2.getId(), is(1));
assertThat(a2.getName(), is("Hello!"));

values.put("types", "C,D,F");

final A a3 = QueryParamMapper.fromQueryParams(objectMapper, values, A.class);

assertThat(a3.getId(), is(1));
assertThat(a3.getName(), is("Hello!"));
assertThat(a3.getTypes(), contains("C", "D", "F"));

values.put("animals", "SHEEP,DOG");

final A a4 = QueryParamMapper.fromQueryParams(objectMapper, values, A.class);

assertThat(a4.getId(), is(1));
assertThat(a4.getName(), is("Hello!"));
assertThat(a4.getTypes(), contains("C", "D", "F"));
assertThat(a4.getAnimals(), containsInAnyOrder(Animal.SHEEP, Animal.DOG));

// bad enum value?
values.put("animals", "NOTFOUND");

try {
QueryParamMapper.fromQueryParams(objectMapper, values, A.class);
} catch (IllegalArgumentException e) {
// expected
}
}

}

0 comments on commit b9b8203

Please sign in to comment.