Spring boot Jackson dynamic partial response - json

I am working on rest service using Spring-Boot 1.3. In this, I have to return partial response based on fields(to include) provided in request input parameter(e.g. ../employees?opFields=name,emailId,..).
I want to implement jackson.antpathfilter (An implementation to add filtering based on AntPath matching). I have to add configuration such that I don't need to change return type of Rest(Controller)'s service method. But based on the object instance of particular class, serialize using filter else use normal serialization. Filter should be applied to instance of particular class only.
Update
Basically I want to implement dynamic partial response with,
1) Retrieving opFields dynamically from request.
2) Setting filter based on object type(can be antpathbuilder or simple)
3) Not changing return type of (rest)controller method.
As of now I have added configuration as below, but its giving issue in ExceptionHandler.
#Configuration
public class CustomDispatcherServlet extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> messageConverters) {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().mixIn(Object.class, AntPathFilterMixin.class).build();
messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));
extendMessageConverters(messageConverters);
}
}
I am extending MappingJacksonValue as below and using class object to send rest call response,
public class FilteredResponse extends MappingJacksonValue {
public FilteredResponse(final Object value, final String... opFields) {
super(value);
if (null == opFields || opFields.length <= 0) {
setFilters(new SimpleFilterProvider().addFilter("antPathFilter", new AntPathPropertyFilter("**")));
} else {
setFilters(new SimpleFilterProvider().addFilter("antPathFilter", new AntPathPropertyFilter(opFields)));
}
}
}
Doing so, giving me more issues, when object is not JacksonResponse class. Also, I have to create object at every controller method and change return type where partial response required.
Can we check object instance dynamically and set filter. Or any other solution?

Basically, when you extend MappingJacksonValue and set filters.
E.g.
public class PartialResponse extends MappingJacksonValue {
public JacksonResponse(final Object value, final String... filters) {
super(value);
if (null == filters || filters.length <= 0) {
setFilters(new SimpleFilterProvider().addFilter("antPathFilter", new AntPathPropertyFilter("**")));
} else {
setFilters(new SimpleFilterProvider().addFilter("antPathFilter", new AntPathPropertyFilter(filters)));
}
}
}
In this, instead of configuring object mapper with Object.class, if you add configuration for PartialResponse.class, Resolves problem.
#Configuration
public class CustomDispatcherServlet extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> messageConverters) {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().mixIn(PartialResponse.class, AntPathFilterMixin.class).build();
messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));
extendMessageConverters(messageConverters);
}
}

Related

Exclude empty Hateoas "links" in the spring boot restapi response

I have a sample response class that extends RepresentationModel. In some scenarios I don't add any hateoas links in the response. In that case, I'm getting an empty links field in the json response
"links": []
I tried adding "JsonInclude.Include.NON_EMPTY" to the response class, but since the links field is final in RepresentationModel, it's still bringing empty links field in the response.
How can I avoid this empty links field in the response ?
Firstly make sure you have a good reason to use links with media type application/json rather than media type built for hypermedia such as HAL (application/hal+json).
Though RepresentationModel has a field of List<Link>, the getter returns a Links instead of List<Link>. Jackson treats it as simple type (where a JsonSerializer is used) instead of a collection type (where a CollectionSerializer is used), so JsonInclude.Include.NON_EMPTY doesn't work as you expect.
public class RepresentationModel<T extends RepresentationModel<? extends T>> {
private final List<Link> links;
#JsonProperty("links")
public Links getLinks() {
return Links.of(links);
}
}
public class Links implements Iterable<Link> { }
public abstract class JsonSerializer<T> {
public boolean isEmpty(SerializerProvider provider, T value) {
return (value == null);
}
}
public class CollectionSerializer {
#Override
public boolean isEmpty(SerializerProvider prov, Collection<?> value) {
return value.isEmpty();
}
}
One solution is override the getter getLinks() and use a customm filter.
class User extends RepresentationModel<User> {
// ...
#JsonProperty("links")
// if links is an empty JSON array, exclude it
#JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = EmptyLinksFilter.class)
#Override
public Links getLinks() {
return super.getLinks();
}
}
/* The word "filter" is a bit ambiguous (included? or excluded?).
Here when the equals() of this class return true, the value will be excluded.
Choose a class name to make yourself comfortable. */
class EmptyLinksFilter{
#Override
public boolean equals(Object obj) {
if (obj == null || !(obj instanceof Links)) {
return false;
}
Links links = (Links) obj;
return links.isEmpty();
}
}
The full code is in Github.
Second solution may be custom mixin like what Spring HATEOAS already build for HAL. Related code are:
RepresentationModelMixin
Jackson2HalModule.HalLinkListSerializer
Jackson2HalModule
HalMediaTypeConfiguration
The second solution is much complicated. That's why I recommand media types like HAL, for which Spring HATEOAS already has good configuration.
As per the answer from #yejianfengblue, I have created a custom representation model as below and extended this CustomRepresentationModel from response java classes instead of Hateoas RepresentationModel.
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.lang.NonNull;
public class CustomRepresentationModel<T extends CustomRepresentationModel<? extends T>> extends
RepresentationModel<T> {
#JsonProperty("_links")
#JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = NonEmptyLinksFilter.class)
#NonNull
#Override
public Links getLinks() {
return super.getLinks();
}
static class NonEmptyLinksFilter {
#Override
public boolean equals(Object obj) {
if (!(obj instanceof Links)) {
return false;
}
Links links = (Links) obj;
return links.isEmpty();
}
#Override
public int hashCode() {
return super.hashCode();
}
}
}

Is it possible to pass a java.util.Stream to Gson?

I'm currently working on a project where I need to fetch a large amount of data from the Database and parse it into a specific Json format, I already have built my custom Serializers and Its working properly when i pass a List to Gson. But as I was already working with Streams from my JPA Layer, I thought I could pass the Stream down to the Gson parser so that it could transform it directly to my Json data. But I'm getting an empty Json object instead of a correctly populated one.
So, if anyone could point to me a way to make Gson work with Java 8 Streams or if this isn't possible currently.. i could not find anything on Google, so i came to Stackoverflow.
You could use JsonWriter to streaming your data to output stream:
public void writeJsonStream(OutputStream out, Stream<DataObject> data) throws IOException {
try(JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"))) {
writer.setIndent(" ");
writer.beginArray();
data.forEach(d -> {
d.beginObject();
d.name("yourField").value(d.getYourField());
....
d.endObject();
});
writer.endArray();
}
}
Note that you're in charge of controling the json structure.
That is, if your DataObject contains nested Object, you have to write beginObject()/endObject() respectively. The same goes for nested array.
It is not as trivial as one would expect, but it can be done in a generic way.
When you look into the Javadoc to TypeAdapterFactory, they provide a very simplistic way of writing a TypeAdapterFactory for a custom type. Alas, it does not work as expected because of problems with element type detection. The proper way to do this can be found in Gson-internal CollectionTypeAdapterFactory. It is quite complex, but taking what's necessary one can come up with something like that:
final class StreamTypeAdapterFactory implements TypeAdapterFactory {
#SuppressWarnings("unchecked")
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
Class<? super T> rawType = typeToken.getRawType();
if (!Stream.class.isAssignableFrom(rawType)) {
return null;
}
Type elementType = ExtraGsonTypes.getStreamElementType(type, rawType);
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
return (TypeAdapter<T>) new StreamTypeAdapter<>(elementAdapter);
}
private static class StreamTypeAdapter<E> extends TypeAdapter<Stream<E>> {
private final TypeAdapter<E> elementAdapter;
StreamTypeAdapter(TypeAdapter<E> elementAdapter) {
this.elementAdapter = elementAdapter;
}
public void write(JsonWriter out, Stream<E> value) throws IOException {
out.beginArray();
for (E element : iterable(value)) {
elementAdapter.write(out, element);
}
out.endArray();
}
public Stream<E> read(JsonReader in) throws IOException {
Stream.Builder<E> builder = Stream.builder();
in.beginArray();
while (in.hasNext()) {
builder.add(elementAdapter.read(in));
}
in.endArray();
return builder.build();
}
}
private static <T> Iterable<T> iterable(Stream<T> stream) {
return stream::iterator;
}
}
The ExtraGsonTypes is a special class that I used to circumvent package-private access to $Gson$Types.getSupertype method. It's a hack that works if you're not using JDK 9's modules - you simply place this class in the same package as $Gson$Types:
package com.google.gson.internal;
import java.lang.reflect.*;
import java.util.stream.Stream;
public final class ExtraGsonTypes {
public static Type getStreamElementType(Type context, Class<?> contextRawType) {
return getContainerElementType(context, contextRawType, Stream.class);
}
private static Type getContainerElementType(Type context, Class<?> contextRawType, Class<?> containerSupertype) {
Type containerType = $Gson$Types.getSupertype(context, contextRawType, containerSupertype);
if (containerType instanceof WildcardType) {
containerType = ((WildcardType)containerType).getUpperBounds()[0];
}
if (containerType instanceof ParameterizedType) {
return ((ParameterizedType) containerType).getActualTypeArguments()[0];
}
return Object.class;
}
}
(I filed an issue about that in GitHub)
You use it in the following way:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(new StreamTypeAdapterFactory())
.create();
System.out.println(gson.toJson(Stream.of(1, 2, 3)));

binding configuration to Names in guice recursively

My application has configurations which are loaded using parsing annotations into a file using Jackson's fasterxml annotations. For example:
public class RootConfiguration extends Configuration {
#JsonProperty
#NotEmpty
public String foo;
#JsonProperty
public BarConfiguration bar;
public class BarConfiguration extends Configuration {
#JsonProperty
public String baz;
}
}
The configuration is then injected into providers in my Module that help me bind those properties to places in the code that use them. Like so:
#Provides
#Named("config")
public RootConfiguration provideRootConfiguration(RootConfiguration configuration) {
return configuration;
}
#Provides
#Named("config.foo")
public String provideFooConfiguration(RootConfiguration configuration) {
return configuration.foo;
}
#Provides
#Named("config.bar")
public BarConfiguration provideBarConfiguration(RootConfiguration configuration) {
return configuration.bar;
}
And so on.
I'm looking for a framework to help me avoid this tedious work.
I would imagine something that looks like this:
#Configuration(value = "config", bindSubProperties = true)
public class RootConfiguration extends Configuration {
...
That would use Reflection to bind any sub fields in my class as guice Names.
I've looked into Governator's annotations for configurations but as far as I can see they need to be applied to every configuration that I want to bind, which saves me some coding, but is essentially the same (I still have to manually specify the path for each and every configuration I want to bind).
Before I roll out my own implementation for this, is there something that will give me what I need?
Note: I'm using this for a Dropwizard project so the constraint on using Jackson to map the configuration to POJOs is rather tight (unless I move the application configuration outside of the config yaml).
I don't know of any tool that would do this for you, but you could do it yourself pretty easily with something like this:
void bindConfiguration() {
for (Field field : RootConfiguration.class.getFields() {
bindConfiguration(TypeLiteral.get(field.getGenericType()), field);
}
}
<T> void bindConfiguration(TypeLiteral<T> type, Field field) {
bind(type)
.annotatedWith(Names.named("config." + field.getName()))
.toProvider(new ConfigurationProvider<T>(field))
.in(Singleton.class);
}
class ConfigurationProvider<T> implements Provider<T> {
private final Field field;
#Inject RootConfiguration configuration;
ConfigurationProvider(Field field) {
this.field = field;
}
#Override
public T get() {
return (T) field.get(configuration);
}
}

Change the json DateTime serialization in WCF 4.0 REST Service

I need to replace the DateTime serialization for JSON in WCF REST Self Hosted service. Right now, I'm using something like the following code to do it, but it's definitely not the way to go since it requires manipulating each class.
[DataContract]
public class Test
{
[IgnoreDataMember]
public DateTime StartDate;
[DataMember(Name = "StartDate")]
public string StartDateStr
{
get { return DateUtil.DateToStr(StartDate); }
set { StartDate = DateTime.Parse(value); }
}
}
where my utility function DateUtil.DateToStr does all the formatting work.
Is there any easy way to do it without having to touch the attributes on my classes which have the DataContract attribute? Ideally, there would be no attributes, but a couple of lines of code in my configuration to replace the serializer with one where I've overridden DateTime serialization.
Everything that I've found looks like I have to replace huge pieces of the pipeline.
This article doesn't appear to apply because in I'm using WebServiceHost not HttpServiceHost, which not part of the 4.5.1 Framework.
JSON.NET Serializer for WCF REST Services
By default WCF uses DataContractJsonSerializer to serialize data into JSON. Unfortunatelly date from this serializer is in very difficult format to parse by human brain.
"DateTime": "\/Date(1535481994306+0200)\/"
To override this behavior we need to write custom IDispatchMessageFormatter. This class will receive all data which should be returned to requester and change it according to our needs.
To make it happen to the operations in the endpoint add custom formatter - ClientJsonDateFormatter:
ServiceHost host=new ServiceHost(typeof(CustomService));
host.AddServiceEndpoint(typeof(ICustomContract), new WebHttpBinding(), Consts.WebHttpAddress);
foreach (var endpoint in host.Description.Endpoints)
{
if (endpoint.Address.Uri.Scheme.StartsWith("http"))
{
foreach (var operation in endpoint.Contract.Operations)
{
operation.OperationBehaviors.Add(new ClientJsonDateFormatter());
}
endpoint.Behaviors.Add(new WebHttpBehavior());
}
}
ClientJsonDateFormatter is simple class which just applies formatter ClientJsonDateFormatter
public class ClientJsonDateFormatter : IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { }
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Formatter = new ResponseJsonFormatter(operationDescription);
}
public void Validate(OperationDescription operationDescription) { }
}
In the formatter we took imput and serialize it with the changed Serializer:
public class ResponseJsonFormatter : IDispatchMessageFormatter
{
OperationDescription Operation;
public ResponseJsonFormatter(OperationDescription operation)
{
this.Operation = operation;
}
public void DeserializeRequest(Message message, object[] parameters)
{
}
public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
string json=Newtonsoft.Json.JsonConvert.SerializeObject(result);
byte[] bytes = Encoding.UTF8.GetBytes(json);
Message replyMessage = Message.CreateMessage(messageVersion, Operation.Messages[1].Action, new RawDataWriter(bytes));
replyMessage.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw));
return replyMessage;
}
}
And to send information to client we need data writer - RawDataWriter. Its implementation is simple:
class RawDataWriter : BodyWriter
{
byte[] data;
public RawDataWriter(byte[] data)
: base(true)
{
this.data = data;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartElement("Binary");
writer.WriteBase64(data, 0, data.Length);
writer.WriteEndElement();
}
}
Applying all code will result in returning date in more friendly format:
"DateTime":"2018-08-28T20:56:48.6411976+02:00"
To show it in practice I created example in the github branch DateTimeFormatter.
Please check also this answer as very likely you also will need it.
There is a limitation in JSON to convert DateTime, specially according to your case.
Please see http://msdn.microsoft.com/en-us/library/bb412170(v=vs.110).aspx
and read the section Dates/Times and JSON
To resolve this problem, I simply changed the type of serialization from JSON to XML for all the calls including DateTime.
After long time discussion ,I have find out the solution for it.
Please Use the following Code to Solve serialized date..
[IgnoreDataMember]
public DateTime? PerformanceDate { get; set; }
[DataMember(EmitDefaultValue = false, Name = "PerformanceDate")]
public string UpdateStartDateStr
{
get
{
if (this.PerformanceDate.HasValue)
return this.PerformanceDate.Value.ToUniversalTime().ToString("s", CultureInfo.InvariantCulture);
else
return null;
}
set
{
// should implement this...
}
}

Jersey / JAXB: Unmarshaling of empty json array results in a list with one item where all fields are set to null

I have a really simple rest web service returning a list of questions. This code works as expected when the number of questions returned are greater than zero. But if the server returns an empty json array like [], JAXB creates a list with one question instance where all fields are set to null!
I'm new to both Jersey and JAXB so I don't know whether I haven't configured it correctly or whether this is a known problem. Any tips?
Client configuration:
DefaultApacheHttpClientConfig config = new DefaultApacheHttpClientConfig();
config.getProperties().put(DefaultApacheHttpClientConfig.PROPERTY_HANDLE_COOKIES, true);
config.getClasses().add(JAXBContextResolver.class);
//config.getClasses().add(JacksonJsonProvider.class); // <- Jackson causes other problems
client = ApacheHttpClient.create(config);
JAXBContextResolver:
#Provider
public final class JAXBContextResolver implements ContextResolver<JAXBContext> {
private final JAXBContext context;
private final Set<Class> types;
private final Class[] cTypes = { Question.class };
public JAXBContextResolver() throws Exception {
this.types = new HashSet(Arrays.asList(cTypes));
this.context = new JSONJAXBContext(JSONConfiguration.natural().build(), cTypes);
}
#Override
public JAXBContext getContext(Class<?> objectType) {
return (types.contains(objectType)) ? context : null;
}
}
Client code:
public List<Question> getQuestionsByGroupId(int id) {
return digiRest.path("/questions/byGroupId/" + id).get(new GenericType<List<Question>>() {});
}
The Question class is just a simple pojo.
I know this is not exactly an answer to your question, but I choosed to use GSON on top of jersey, for my current projects. (and I try to avoid JAXB as much as possible), and I found it very easy and resilient.
You just have to declare
#Consumes(MediaType.TEXT_PLAIN)
or
#Produces(MediaType.TEXT_PLAIN)
or both, and use the GSON marshaller/unmarshaller, and work with plain Strings. Very easy to debug, unittest too...
Using Jackson may help.
See org.codehaus.jackson.map.ObjectMapper and org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion.NON_EMPTY
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize;
public class SampleContextResolver implements ContextResolver<ObjectMapper>
{
#Override
public ObjectMapper getContext(Class<?> type)
{
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationConfig(mapper.getSerializationConfig()
.withSerializationInclusion(JsonSerialize.Inclusion.NON_EMPTY)
}
}