How to ignore(/hide) attributes of class in JSON response - java

I am new to Java RESTful web services. I have classes structure as following:
class BaseClass{
String basicInfo;
}
class DataClass extends BaseClass{
String id;
}
class DataList extends BaseClass{
List<DataClass> dataList;
}
when I get DataList class as Response of web-service, I get it in following format:
{
"basicInfo" : "Mandetory",
"dataList" : [
{
"basicInfo" : "Optional",
"id" : "I_1001"
},
{
"basicInfo" : "Mandetory",
"id" : "I_1002"
}
]
}
But I want to ignore "basicInfo" attribute in every Data Object of dataList.
ex.
{
"basicInfo" : "Mandetory",
"dataList" : [
{
"id" : "I_1001"
},
{
"id" : "I_1002"
}
]
}
Is there any way through which I could ignore these attributes(using annotations)?
Note : I can't change the class structures.

#JsonIgnore
the above is the annotation you require. for more variations of this you can go through the following link. add the above annotation to the fields which you wish to ignore in your transfer object and it will be ignore when parsing. Since you haven't mentioned which library you are using to work with json, i have answered your question using Jackson Library.

You can use JAX-B annotation #XmlTransient on your attribute basicInfo.
Alternatively, when JSON-B (JSR 367) will be available (Java EE 8), you can use the #JsonbTransient or, even better, the #JsonbVisibility attribute.

Related

How to aggregate in spring data mongo db a nested object and avoid a PropertyReferenceException?

I am creating a new endpoint in springboot that will return simple stats on users generated from an aggregate query in a mongo database. However I get a PropertyReferenceException. I have read multiple stackoverflow questions about it, but didn't find one that solved this problem.
We have a mongo data scheme like this:
{
"_id" : ObjectId("5d795993288c3831c8dffe60"),
"user" : "000001",
"name" : "test",
"attributes" : {
"brand" : "Chrome",
"language" : "English" }
}
The database is filled with multiple users and we want using Springboot aggregate the stats of users per brand. There could be any number of attributes in the attributes object.
Here is the aggregation we are doing
Aggregation agg = newAggregation(
group("attributes.brand").count().as("number"),
project("number").and("type").previousOperation()
);
AggregationResults<Stats> groupResults
= mongoTemplate.aggregate(agg, Profile.class, Stats.class);
return groupResults.getMappedResults();
Which produces this mongo query which works:
> db.collection.aggregate([
{ "$group" : { "_id" : "$attributes.brand" , "number" : { "$sum" : 1}}} ,
{ "$project" : { "number" : 1 , "_id" : 0 , "type" : "$_id"}} ])
{ "number" : 4, "type" : "Chrome" }
{ "number" : 2, "type" : "Firefox" }
However when running a simple integration test we get this error:
org.springframework.data.mapping.PropertyReferenceException: No property brand found for type String! Traversed path: Profile.attributes.
From what I understand, it seems that since attributes is a Map<String, String> there might be a schematic problem. And in the mean time I can't modify the Profile object.
Is there something I am missing in the aggregation, or anything I could change in my Stats object?
For reference, here are the data models we're using, to work with JSON and jackson.
The Stats data model:
#Document
public class Stats {
#JsonProperty
private String type;
#JsonProperty
private int number;
public Stats() {}
/* ... */
}
The Profile data model:
#Document
public class Profiles {
#NotNull
#JsonProperty
private String user;
#NotNull
#JsonProperty
private String name;
#JsonProperty
private Map<String, String> attributes = new HashMap<>();
public Stats() {}
/* ... */
}
I found a solution, which was a combination of two problems:
The PropertyReferenceException was indeed caused because attributes is a Map<String, String> which means there is no schemes for Mongo.
The error message No property brand found for type String! Traversed path: Profile.attributes. means that the Map object doesn't have a brand property in it.
In order to fix that without touching my orginal Profile class, I had to create a new custom class which would map the attributes to an attributes object having the properties I want to aggreate on like:
public class StatsAttributes {
#JsonProperty
private String brand;
#JsonProperty
private String language;
public StatsAttributes() {}
/* ... */
}
Then I created a custom StatsProfile which would leverage my StatsAttributes and would be similar to the the original Profile object without modifying it.
#Document
public class StatsProfile {
#JsonProperty
private String user;
#JsonProperty
private StatsAttributes attributes;
public StatsProfile() {}
/* ... */
}
With that I made disapear my problem with the PropertyReferenceException using my new class StatsAggregation in the aggregation:
AggregationResults<Stats> groupResults
= mongoTemplate.aggregate(agg, StatsProfile.class, Stats.class);
However I would not get any results. It seems the query would not find any document in the database. That's where I realied that production mongo objects had the field "_class: com.company.dao.model.Profile" which was tied to the Profile object.
After some research, for the new StatsProfile to work it would need to be a #TypeAlias("Profile"). After looking around, I found that I also needed to precise a collection name which would lead to:
#Document(collection = "profile")
#TypeAlias("Profile")
public class StatsProfile {
/* ... */
}
And with all that, finally it worked!
I suppose that's not the prettiest solution, I wish I would not need to create a new Profile object and just consider the attributes as a StatsAttributes.class somehow in the mongoTemplate query. If anyone knows how to, please share 🙏

Java Json serialization of Interface types

I have a json webservice I'm calling with a spring rest template. One of the request parameters is an interface. I'm looking for the right combination of annotations (either Jackson or Jaxb) that will create my json request.
My request needs to look like this:
{
"request": {
"specificAccountIdentifier": {
"field1" : "value",
"field2" : "value"
}
}
}
However, right now, it's marshaling as this:
{
"request": {
"accountIdentifier": {
"field1" : "value",
"field2" : "value"
}
}
}
Request Class:
#XmlRootElement
#XmlType
#XmlAccessorType(XmlAccessType.FIELD)
public class Request {
#XmlAnyElement
#XmlElementRefs({#XmlElementRef(type = SpecificAccountIdentifier.class)})
private AccountIdentifier accountIdentifier;
public Request() {
}
}
AccountIdentifier:
#XmlSeeAlso(SpecificAccountIdentifier.class)
public interface AccountIdentifier extends Serializable {
}
SpecificAccountIdentifier:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement
#XmlType
public class SpecificAccountIdentifier implements AccountIdentifier {
private static final long serialVersionUID = 8894475816041559L;
private Long field1;
private Long field2;
...class details...
}
I've tried a few different combinations of #JsonTypeInfo, but can't get anything to work right.
UPDATE:
Even after looking into the answer below, I still could not get anything to work, so I ended up just writing my own custom Serializer.
Found similar question asked 4 years ago but it seems you can use it to kick start the idea behind.
Basically it is using EclipseLink JAXB (MOXy) and a factory method to generate the Implementation of your interface.
https://stackoverflow.com/a/16155520/6039974

Dynamic JSON serialization based on the property value

I am implementing a REST server using Spring Boot, JAXB2 and Jackson. This server supports JSON and XML and it is based on official specifications.
I am currently having a serialization problem specific to the JSON format and I do not know how it can be solved?
The specification defines several primitive types as string, integer, etc. that can be extended and depending of this extension, the result of the serialization is not the same.
The Java classes
public class PrimitiveType {
#XmlAttribute
private String id
#XmlElement(name = "extension")
private List<Extension> extensions = new ArrayList<>();
// Getters and Setters
}
public class StringType extends PrimitiveType {
#XmlAttribute
private String value;
// Getter and Setter
}
JSON
// Without id and/or extension(s)
"code" : "abc"
// With id and/or extension(s)
"code ": "abc",
"_code": {
"id": "1",
"extension" : [ {
"url" : "http://mydomain/ext/1",
"valueString" : "abc-1"
}]
}
I do not have any problem with the XML but it is not the same for the JSON. I do not know how I can add a property dynamically on the same level. I had a look on the JsonSerializer but it seems that it allows to change the serialization on the object itself.
Does anyone had a chance to do this kind of things before?

Jersey JSON parsing with root name of class name

I have a pojo class.
#XmlRootElement(name = "project")
public class Project {
private UUID id;
private String label;
private String name;
//getters and setters
}
It accepts this as input if I try from postman.
{
"label" : "label",
"name" : "name"
}
But, I want it to accept values as
{
"project" : {
"label" : "label",
"name" : "name"
}
}
My endpoint method is
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response createProject(Project project) {
log.info("createProject called." + project);
if (project == null) {
return Response.ok(false).build();
}
ps = new ProjectServiceImpl();
return Response.ok(ps.createProject(project)).build();
}
There are various other methods which are like this, like get all projects, where other party expects 'root-name' or 'class-name' like this at the start of json. I am stuck with things like this and it's slowing down my work. Please suggest me something or provide any source where I can read the stuff.
I think SerializationFeature.WRAP_ROOT_VALUE and DeserializationFeature.UNWRAP_ROOT_VALUE are configuration options that you are looking for.
Read more here:
https://github.com/FasterXML/jackson-databind/wiki/Serialization-Features
https://github.com/FasterXML/jackson-databind/wiki/Deserialization-Features
Please use #XmlRootElement instead of #XmlRootElement(name = "project") and try it out
A good read if you are using spring and consuming a rest service - http://spring.io/guides/gs/consuming-rest/

Spring Data Elastic Search Nested Field and NativeSearchQueryBuilder.withFields

I can't seem to get Nested Fields to return when I use the NativeSearchQueryBuilder.withFields(...) method.
Here is my parent Object:
#Document(indexName = "inventory")
public class Inventory
{
#Id
private String id;
#Field(type=FieldType.String)
private String name;
#Field(type=FieldType.Nested, index=FieldIndex.not_analyzed, store=true)
private List<Model> models;
}
And here is the nested Object:
public class Model
{
#Field(type=FieldType.String, index=FieldIndex.not_analyzed, store=true)
private String model;
#Field(type=FieldType.String, index=FieldIndex.not_analyzed, store=true)
private Set<String> series;
}
And the Query
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery());
nativeSearchQueryBuilder.withFields("models.series");
NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
FacetedPage<Inventory> results = inventoryRepository.search(nativeSearchQuery);
Resulting TotalElements = 529
But each Object in the Content Looks like this (in JSON format):
{
"id":"d5f82880-15bc-45ed-8abb-ff97d0e45da9",
"name": null,
"models": null
}
If I remove the withFields(...) setting, I get back:
{
"id":"d5f82880-15bc-45ed-8abb-ff97d0e45da9",
"name": "Cool Beans",
"models": [
{
"model" : "foo",
"series" : ["bar"]
}
]
}
I've tried models, models.model, models.series, model, series. I can't get withFields working with NestedFields.
Any thoughts?
My understanding of elastic search fields was incorrect. rahulroc tipped me off.
withFields is not the same as source filtering.
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-fields.html
So i'm effectively telling Spring Data ES to do this:
curl localhost:9200/inventory/_search?pretty=true -d '
{
"fields" : ["models.series"],
"query" : {
"match" : {"name" : "cool"}
}
}'
When this is what I want
curl localhost:9200/inventory/_search?pretty=true -d '
{
"_source" : ["models.series"],
"query" : {
"match" : {"name" : "cool"}
}
}'
The withFields approach worked for what I was doing up till I started adding NestedFields. And the current implementation of Spring Data ES that I'm using does not support source filtering.
Source Filtering was just recently added to Spring Data ES 2.0.0.RC1

Categories

Resources