I have a few service calls that have to receive POST bodies in x-www-form-urlencoded format. Is there a recommended approach for creating a MessageSerializer for those, ideally in a somewhat generic manner (suitable for a MessageSerializer factory)?
Thanks!
inginia
(Anyaebosi Tobenna)
January 18, 2019, 8:24am
2
Hi Stephen, is there something you’ve tried that worked? I’d like to see how you went about it.
ihostage
(Sergey Morgunov)
January 18, 2019, 10:07am
3
Try to use FormUrlEncodedParser#parseAsJavaArrayValues
for parsing body.
Hi Anyaebosi,
I haven’t executed an implementation yet, I’ve been in a preliminary research mode as it’s not something we’ve absolutely needed yet, but it’s coming up soon.
So far I’ve not had any concrete ideas. My instincts make me feel like maybe there’s some way of making Jackson JSON help even though the data isn’t JSON, but I’ve not really made a lot of use of that library lately so while I suspect there’s something it can help with in terms of object serialization/deserialization I’m not sure where/how.
inginia
(Anyaebosi Tobenna)
January 18, 2019, 3:09pm
5
Here’s a SerializerFactory
that can handle x-www-form-urlencoded
content-type. Maybe not optimal but it gives an idea of one way to go about it.
public class FormSerializerFactory implements SerializerFactory {
@Override
public <MessageEntity> StrictMessageSerializer<MessageEntity> messageSerializerFor(Type type) {
return new GenericMessageSerializer<>(type);
}
}
public class GenericMessageSerializer<MessageEntity>
implements StrictMessageSerializer<MessageEntity> {
private final Type type;
private final ObjectMapper objectMapper;
public GenericMessageSerializer(Type type) {
this.type = type;
this.objectMapper = new ObjectMapper();
}
@Override
public NegotiatedSerializer<MessageEntity, ByteString> serializerForRequest() {
return new PlainTextSerializer("utf-8");
}
@Override
public NegotiatedDeserializer<MessageEntity, ByteString> deserializer(
MessageProtocol protocol) throws UnsupportedMediaType {
if (protocol.contentType().isPresent()) {
if (protocol.contentType().get().equals("application/x-www-form-urlencoded")) {
return new FormEncodedDeserializer(type, objectMapper);
} else if (protocol.contentType().get().equals("application/json")) {
return new JsonDeserializer(type, objectMapper);
} else if (protocol.contentType().get().equals("text/plain")) {
return new PlainTextDeserializer("utf-8");
} else {
throw new UnsupportedMediaType(protocol, new MessageProtocol().withContentType("text/plain"));
}
} else {
return new PlainTextDeserializer("utf-8");
}
}
@Override
public NegotiatedSerializer<MessageEntity, ByteString> serializerForResponse(
List<MessageProtocol> acceptedMessageProtocols) throws NotAcceptable {
if (acceptedMessageProtocols.isEmpty()) {
return new PlainTextSerializer("utf-8");
} else {
for (MessageProtocol protocol: acceptedMessageProtocols) {
if (protocol.contentType().isPresent()) {
String contentType = protocol.contentType().get();
if (contentType.equals("text/plain") || contentType.equals("text/*") || contentType.equals("*/*")) {
return new PlainTextSerializer(protocol.charset().orElse("utf-8"));
} else if (protocol.contentType().get().equals("application/json")) {
return new JsonSerializer();
}
} else {
return new PlainTextSerializer(protocol.charset().orElse("utf-8"));
}
}
throw new NotAcceptable(acceptedMessageProtocols, new MessageProtocol().withContentType("text/plain"));
}
}
public class PlainTextSerializer implements NegotiatedSerializer<MessageEntity, ByteString> {
private final String charset;
public PlainTextSerializer(String charset) {
this.charset = charset;
}
@Override
public MessageProtocol protocol() {
return new MessageProtocol(
Optional.of("text/plain"),
Optional.of(charset),
Optional.empty()
);
}
@Override
public ByteString serialize(MessageEntity s) throws SerializationException {
if (s instanceof String) {
return ByteString.fromString((String) s, charset);
}
try {
return ByteString.fromString(objectMapper
.writerFor(objectMapper.constructType(type)).writeValueAsString(s));
} catch (JsonProcessingException e) {
throw new SerializationException(e);
}
}
}
private class JsonSerializer implements NegotiatedSerializer<MessageEntity, ByteString> {
private final ObjectMapper mapper = new ObjectMapper();
@Override
public MessageProtocol protocol() {
return new MessageProtocol(
Optional.of("application/json"),
Optional.empty(), Optional.empty());
}
@Override
public ByteString serialize(MessageEntity s) throws SerializationException {
try {
return ByteString.fromArray(mapper.writeValueAsBytes(s));
} catch (JsonProcessingException e) {
throw new SerializationException(e);
}
}
}
private class FormEncodedSerializer implements
NegotiatedSerializer<String, ByteString> {
@Override
public MessageProtocol protocol() {
return new MessageProtocol(
Optional.of("application/x-www-form-urlencoded"),
Optional.empty(), Optional.empty());
}
@Override
public ByteString serialize(String entity) throws SerializationException {
return ByteString.fromString(entity);
}
}
private class FormEncodedDeserializer
implements NegotiatedDeserializer<MessageEntity, ByteString> {
private final ObjectMapper objectMapper;
private final JavaType javaType;
public FormEncodedDeserializer(Type type, ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
this.javaType = objectMapper.constructType(type);
}
@Override
public MessageEntity deserialize(ByteString byteString) throws DeserializationException {
final String string
= byteString.decodeString("utf-8");
final String decodedString;
try {
decodedString = URLDecoder.decode(string, "utf-8");
} catch (UnsupportedEncodingException e) {
throw new DeserializationException(e);
}
final Map<String, String> nameValuePair = Arrays
.stream(decodedString.split("&"))
.collect(Collectors.toMap(
token -> token.split("=")[0],
token -> token.split("=")[1]
));
return objectMapper.convertValue(nameValuePair, javaType);
}
}
private class JsonDeserializer
implements NegotiatedDeserializer<MessageEntity, ByteString> {
private final ObjectMapper mapper;
private final JavaType javaType;
public JsonDeserializer(Type type, ObjectMapper mapper) {
this.mapper = mapper;
this.javaType = mapper.constructType(type);
}
@Override
public MessageEntity deserialize(ByteString byteString)
throws DeserializationException {
try {
return mapper.readValue(
byteString.iterator().asInputStream(), javaType);
} catch (IOException e) {
throw new DeserializationException(e);
}
}
}
private class PlainTextDeserializer implements NegotiatedDeserializer<MessageEntity, ByteString> {
private final String charset;
public PlainTextDeserializer(String charset) {
this.charset = charset;
}
@Override
public MessageEntity deserialize(ByteString byteString) throws DeserializationException {
return (MessageEntity) byteString.decodeString(charset);
}
}
}