Skip to content

Commit

Permalink
Added logic for valueless booleans
Browse files Browse the repository at this point in the history
  • Loading branch information
trickl committed Sep 11, 2019
1 parent 56490a1 commit 8c0478c
Show file tree
Hide file tree
Showing 19 changed files with 377 additions and 302 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.github.trickl.jackson.module.httpquery.annotations.HttpQuery;
import com.github.trickl.jackson.module.httpquery.annotations.HttpQueryDelimited;
import com.github.trickl.jackson.module.httpquery.deser.HttpQueryDeserializer;
import com.github.trickl.jackson.module.httpquery.ser.HttpQueryDelimitedSerializer;
import com.github.trickl.jackson.module.httpquery.ser.HttpQuerySerializer;

public class HttpQueryAnnotationIntrospector extends AnnotationIntrospector implements Versioned {
Expand All @@ -20,7 +18,7 @@ public JsonSerializer<?> findSerializer(Annotated am) {
if (am.hasAnnotation(HttpQuery.class)) {
HttpQuery annotation = am.getAnnotation(HttpQuery.class);
return new HttpQuerySerializer(
am.getType(),
am.getType(),
annotation.includeQuestionMark(),
annotation.encodeNames(),
annotation.encodeValues());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;

public class HttpQueryModule extends Module {

Expand All @@ -18,7 +17,6 @@ public Version version() {

@Override
public void setupModule(SetupContext context) {
context.appendAnnotationIntrospector(
new HttpQueryAnnotationIntrospector());
context.appendAnnotationIntrospector(new HttpQueryAnnotationIntrospector());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,18 @@
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface HttpQuery {
/**
* Whether a question mark is included at the beginning
* of the query.
*/
/** Whether a question mark is included at the beginning of the query. */
boolean includeQuestionMark() default true;

/**
* Whether to URI encode parameter names.
*/
/** Whether to URI encode parameter names. */
boolean encodeNames() default true;

/**
* Whether to URI encode parameter values.
*/
/** Whether to URI encode parameter values. */
boolean encodeValues() default true;

/**
* Whether to ignore unknown parameters when deserializing.
*
* @return
*/
boolean ignoreUnknown() default true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,12 @@
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface HttpQueryDelimited {
/**
* The delimiter for this list.
*/
/** The delimiter for this list. */
String delimiter() default ",";

/**
* Whether to URI encode parameter values.
*/
/** Whether to URI encode parameter values. */
boolean encodeValues() default true;

/**
* Whether to URI encode the supplied delimiter.
*/
/** Whether to URI encode the supplied delimiter. */
boolean encodeDelimiter() default true;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.github.trickl.jackson.module.httpquery.annotations;

import com.fasterxml.jackson.annotation.JacksonAnnotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface HttpQueryNoValue {}
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,13 @@
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.PropertyName;
import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBuilder;
import com.fasterxml.jackson.databind.deser.BeanDeserializerFactory;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.github.trickl.jackson.module.httpquery.annotations.HttpQueryDelimited;

import com.github.trickl.jackson.module.httpquery.annotations.HttpQueryNoValue;
import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
Expand All @@ -49,10 +47,7 @@ public class HttpQueryDeserializer extends StdDeserializer<Object> {

/** Create a deserializer for converting a Http query string to a typed object. */
public HttpQueryDeserializer(
JavaType javaType,
boolean ignoreUnknown,
boolean decodeNames,
boolean decodeValues) {
JavaType javaType, boolean ignoreUnknown, boolean decodeNames, boolean decodeValues) {
super(Object.class);
this.javaType = javaType;
this.ignoreUnknown = ignoreUnknown;
Expand All @@ -76,23 +71,23 @@ public Object deserialize(JsonParser jp, DeserializationContext ctxt)
if (queryString.startsWith("?")) {
queryString = queryString.substring(1, queryString.length());
}

BeanDeserializer beanDeserializer = getBeanDeserializer(ctxt, javaType);
ValueInstantiator valueInstantiator = beanDeserializer.getValueInstantiator();
final Object bean = valueInstantiator.createUsingDefault(ctxt);

String[] nameValueParams = queryString.split("&");
Map<String, List<String>> params = new HashMap<>();
for (String nameValueParam : nameValueParams) {
for (String nameValueParam : nameValueParams) {
String name;
String encodedValue = null;
if (nameValueParam.contains("=")) {
String encodedName = nameValueParam.substring(0, nameValueParam.indexOf('='));
name = decodeNames ? decode(encodedName) : encodedName;
encodedValue = nameValueParam.substring(
nameValueParam.indexOf('=') + 1, nameValueParam.length());
encodedValue =
nameValueParam.substring(nameValueParam.indexOf('=') + 1, nameValueParam.length());
} else {
name = decodeNames ? decode(nameValueParam) : nameValueParam;
name = decodeNames ? decode(nameValueParam) : nameValueParam;
}
params.computeIfAbsent(name, n -> new ArrayList<>());
params.get(name).add(encodedValue);
Expand All @@ -106,8 +101,7 @@ public Object deserialize(JsonParser jp, DeserializationContext ctxt)
} else {
String errorMessage =
MessageFormat.format(
"Unknown parameter \"{0}\" supplied.",
new Object[] {param.getKey()});
"Unknown parameter \"{0}\" supplied.", new Object[] {param.getKey()});
throw new JsonParseException(jp, errorMessage);
}
}
Expand All @@ -126,9 +120,7 @@ private String encode(String value) throws UnsupportedEncodingException {
return URLEncoder.encode(value, StandardCharsets.UTF_8.toString());
}

/**
* Set an object value using the supplied query param.
*/
/** Set an object value using the supplied query param. */
public void deserializeNameValue(
List<String> encodedValues,
SettableBeanProperty prop,
Expand All @@ -139,9 +131,8 @@ public void deserializeNameValue(

String jsonifiedParam = "";
JavaType propType = prop.getType();
boolean isArrayOrCollection =
prop.getType().isTypeOrSubTypeOf(Collection.class)
|| prop.getType().isArrayType();
boolean isArrayOrCollection =
prop.getType().isTypeOrSubTypeOf(Collection.class) || prop.getType().isArrayType();
if (isArrayOrCollection) {
boolean shouldDecode = decodeValues;
HttpQueryDelimited delimited = prop.getAnnotation(HttpQueryDelimited.class);
Expand All @@ -150,7 +141,7 @@ public void deserializeNameValue(
String delimiter = delimited.delimiter();
shouldDecode = delimited.encodeValues();
String encodedDelimiter = delimited.encodeDelimiter() ? encode(delimiter) : delimiter;
encodedValues = Arrays.asList(lastValue.split(encodedDelimiter));
encodedValues = Arrays.asList(lastValue.split(encodedDelimiter));
}

List<String> decodedValues = new ArrayList<>();
Expand All @@ -159,35 +150,41 @@ public void deserializeNameValue(
decodedValues.add(decodedValue);
}

jsonifiedParam = wrapAsArray(encodedValues);
jsonifiedParam = wrapAsArray(encodedValues);
} else {
String encodedLastValue = encodedValues.get(encodedValues.size() - 1);
String lastValue = decodeValues ? decode(encodedLastValue) : encodedLastValue;
if (propType.isPrimitive()) {
String encodedLastValue = encodedValues.get(encodedValues.size() - 1);
String lastValue = decodeValues && encodedLastValue != null
? decode(encodedLastValue) : encodedLastValue;

HttpQueryNoValue annotatedNoValue = prop.getAnnotation(HttpQueryNoValue.class);
if (annotatedNoValue != null) {
jsonifiedParam = "true";
} else if (propType.isPrimitive()) {
jsonifiedParam = lastValue;
} else {
jsonifiedParam = quote(lastValue);
}
}

StringReader reader = new StringReader(jsonifiedParam);
JsonParser parser = new ReaderBasedJsonParser(
getIoContext(),
p.getFeatureMask(),
reader,
p.getCodec(),
getCharsToNameCanonicalizer());
JsonParser parser =
new ReaderBasedJsonParser(
getIoContext(),
p.getFeatureMask(),
reader,
p.getCodec(),
getCharsToNameCanonicalizer());
parser.nextToken();
prop.deserializeAndSet(parser, ctxt, bean);
}

private BeanDeserializer getBeanDeserializer(
DeserializationContext context, JavaType javaType) throws JsonMappingException {
private BeanDeserializer getBeanDeserializer(DeserializationContext context, JavaType javaType)
throws JsonMappingException {
DeserializationConfig config = context.getConfig();
BeanDescription beanDesc = config.introspect(javaType);
BeanDeserializerFactory factory = BeanDeserializerFactory.instance;
BeanDeserializer deserializer = (BeanDeserializer)
factory.createBeanDeserializer(context, javaType, beanDesc);
BeanDeserializer deserializer =
(BeanDeserializer) factory.createBeanDeserializer(context, javaType, beanDesc);
deserializer.resolve(context);
return deserializer;
}
Expand All @@ -206,7 +203,6 @@ private String quote(String value) {
}

private String wrapAsArray(List<String> values) {
return "[" + values.stream().map(this::quote)
.collect(Collectors.joining(" ,")) + "]";
return "[" + values.stream().map(this::quote).collect(Collectors.joining(" ,")) + "]";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
Expand All @@ -21,34 +20,27 @@ public class HttpQueryCollectionSerializer extends StdSerializer<Collection<?>>
private final boolean encodeNames;
private final boolean encodeValues;

/**
* Create a serializer for converting an object to an Http query string.
*/
/** Create a serializer for converting an object to an Http query string. */
public HttpQueryCollectionSerializer(
BeanPropertyWriter prop,
boolean encodeNames,
boolean encodeValues) {
BeanPropertyWriter prop, boolean encodeNames, boolean encodeValues) {
super((Class<Collection<?>>) prop.getPropertyType());
this.prop = prop;
this.encodeNames = encodeNames;
this.encodeValues = encodeValues;
}

@Override
public final void serialize(
Collection<?> collection,
JsonGenerator gen,
SerializerProvider provider)
throws IOException {

Collection<?> collection, JsonGenerator gen, SerializerProvider provider) throws IOException {

boolean propertyWritten = false;
try {
for (Object value : collection) {
for (Object value : collection) {
if (propertyWritten) {
gen.writeRaw("&");
}

propertyWritten = serializeAsNameValue(value, prop, gen, provider);
propertyWritten = serializeAsNameValue(value, prop, gen, provider);
}
} catch (Exception e) {
wrapAndThrow(provider, e, "Collection", prop.getName());
Expand All @@ -57,37 +49,34 @@ public final void serialize(

/** Write a property out as "name=value". */
public boolean serializeAsNameValue(
Object propValue,
BeanPropertyWriter prop,
JsonGenerator gen,
SerializerProvider provider)
Object propValue, BeanPropertyWriter prop, JsonGenerator gen, SerializerProvider provider)
throws Exception {

JsonFactory jsonFactory = new JsonFactory();
StringWriter valueWriter = new StringWriter();
String propName = prop.getName();
String name = encodeNames ? encode(propName) : propName;

try (QuotelessStringGenerator valueGenerator = new QuotelessStringGenerator(
jsonFactory.createGenerator(valueWriter))) {
try (QuotelessStringGenerator valueGenerator =
new QuotelessStringGenerator(jsonFactory.createGenerator(valueWriter))) {
if (propValue == null) {
if (prop.willSuppressNulls()) {
if (prop.willSuppressNulls()) {
return false;
} else if (!prop.hasNullSerializer()) {
// Just return the parameter name
gen.writeRaw(name);
return true;
return true;
} else {
provider.findNullValueSerializer(prop)
.serialize(propValue, valueGenerator, provider);
provider.findNullValueSerializer(prop).serialize(propValue, valueGenerator, provider);
}
} else {
} else {
Class<?> cls = propValue.getClass();
provider.findTypedValueSerializer(cls, true, prop)
provider
.findTypedValueSerializer(cls, true, prop)
.serialize(propValue, valueGenerator, provider);
}
}

String value = valueWriter.getBuffer().toString();
gen.writeRaw(name);
gen.writeRaw("=");
Expand All @@ -99,4 +88,4 @@ public boolean serializeAsNameValue(
private String encode(String value) throws UnsupportedEncodingException {
return URLEncoder.encode(value, StandardCharsets.UTF_8.toString());
}
}
}
Loading

0 comments on commit 8c0478c

Please sign in to comment.