I am currently working on an SDK for integrating with an API (as Client). My problem is (I am not even sure this is a problem), but I wanted my request to only contain the parameters that are initialized. Example of how they are being generated now:
{
"scenarioKey":"",
"bulkId":"",
"destinations":[
{
"messageId":"xxxxx",
"to":{
"phoneNumber":""
}
}
],
"sms":null
}
The SMS parameter was never initiated hence I wanted it not to be included in the request body, is that a way I can have a request without this parameter "sms"?
By the way I am using HttpEntity:
HttpEntity<Object> entity = new HttpEntity<>(bodyObject, headers);
Spring boot allows simple configuration of the Jackson ObjectMapper it uses in the application.properties file.
The supported properties are described in the documentation. (https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-customize-the-jackson-objectmapper)
Specifically spring.jackson.default-property-inclusion=non_null in the application.properties should do the trick.
If you want to do this for specific classes or attributes Jackson has the annotation #JsonInclude(Include.NON_NULL).
If you are using jackson to serialize your JSON, you should take a look at the setSerializationInclusion() method on ObjectMapper https://fasterxml.github.io/jackson-databind/javadoc/2.7/com/fasterxml/jackson/databind/ObjectMapper.html#setSerializationInclusion(com.fasterxml.jackson.annotation.JsonInclude.Include)
Here is a simple testcase that prevents the sms field from being included in the JSON output:
#Test
public void testJson() throws Exception {
Addr addr = new Addr();
addr.phoneNumber = "";
Destination destination = new Destination();
destination.messageId = "";
destination.to = addr;
Scenario scenario = new Scenario();
scenario.scenarioKey = "";
scenario.bulkId = "";
scenario.destinations = Arrays.asList(destination);
ObjectMapper mapper = new ObjectMapper()
.enable(SerializationConfig.Feature.INDENT_OUTPUT)
.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
System.out.println(mapper.writeValueAsString(scenario));
}
public static class Scenario {
public String scenarioKey;
public String bulkId;
public List<Destination> destinations;
public String sms;
}
public static class Destination {
public String messageId;
public Addr to;
}
public static class Addr {
public String phoneNumber;
}
Related
I'm trying to determine how to print out a JSON that looks like this, using Java's Jackson library:
{
"status": {
{
"busStatus" : {
"status" : null,
"transactions" : "0",
"retries" : "0",
"failures" : "0"
}
}
}
}
I'm 95% there, but the outermost object is not currently being printed. This is what I'm currently getting outputted:
{
"busStatus" : {
"status" : null,
"transactions" : "0",
"retries" : "0",
"failures" : "0"
}
}
I have a Java class that looks like this:
public class DataClass {
public StatusData status = new StatusData();
public StatusConfig config = new StatusConfig();
public class StatusData {
public SerialStatus busStatus = new SerialStatus();
}
public class StatusConfig {
}
public class SerialStatus {
public String status = null;
public String transactions = "0";
public String retries = "0";
public String failures = "0";
}
}
I'm printing this class to json using the code below:
private DataClass internalData;
ObjectMapper mapper = new ObjectMapper();
status = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(internalData.status);
Is there a way I can configure Jackson to print out the name of the object its serializing into the JSON?
To achieve what you want, you need to print DataClass instead of StatusData. Something like below:
private DataClass internalData = <initialize>;
ObjectMapper mapper = new ObjectMapper();
String data =
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(internalData);
You can use Jackson Filter to control the serialization process, I think it should work with your use case, at least one way to do it.
Use the filter annotation and then create two different filters for your class, where you can define which field to skip, and use it with the ObjectMapper accordingly to convert the whole internalData object, so when you need to skip the status, use one filter and when you need to skip the config associate the other filter with the mapper, while always serializing the parent object. Which should give you the structure you want.
#JsonFilter("filter_serializer")
class User {
public String v1;
public String v2;
}
String[] fieldsToSkip = new String[] { "v1" };
ObjectMapper mapper = new ObjectMapper();
final SimpleFilterProvider filter = new SimpleFilterProvider();
filter.addFilter("filter_serializer",
SimpleBeanPropertyFilter.serializeAllExcept(fieldsToSkip));
User dtoObject = new User();
dtoObject.v1 = "v1";
dtoObject.v2 = "v2";
String jsonStr = mapper.writer(filter).writeValueAsString(dtoObject);
I was able to find the solution I was looking for, from this website.
I've gotten rid of the DataClass and now only have a StatusData and a StatusConfig class. I've included how the StatusData class would look below:
#JsonRootName(value = "status")
public class StatusData {
String status;
String transactions;
// so on
}
To parse the class, I needed to add the JsonRootName annotation above, and also enable a feature on the mapper, as below:
private DataClass internalData;
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE); // don't forget this!
statusText = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(statusObject);
Separately, if you'd like to deserialize a JSON like the one I had into a class like StatusData, do this:
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
statusObject = mapper.readValue(statusText, StatusData.class);
I have created an index (house) with a type "apartments" that contains 20 documents. I uploaded the Json as a binary file into elasticsearch using postman. I have a Spring Boot project that has the following classes:
EsConfig.java - I have configured the clustername which is the default name in the application.properties file.
#Configuration
#EnableElasticsearchRepositories(basePackages = "com.search.repository")
public class EsConfig {
#Value("${elasticsearch.clustername}")
private String EsClusterName;
#Bean
public Client esClient() throws UnknownHostException {
Settings esSettings = Settings.builder()
.put("cluster.name", EsClusterName)
.put("client.transport.sniff", true)
.put("client.transport.ignore_cluster_name", false)
.build();
TransportClient client = new PreBuiltTransportClient(esSettings)
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost"), 9300));
return client;
}
#Bean
public ElasticsearchOperations elasticsearchTemplate() throws Exception{
return new ElasticsearchTemplate(esClient());
}
}
Apartments.java - This is my data model. The documents have the below fields in elasticsearch.
#Document(indexName = "house", type = "apartments")
#JsonIgnoreProperties(ignoreUnknown=true)
public class Apartments {
#Id
private String id;
#JsonProperty("Apartment_Name")
private String apartmentName;
#JsonProperty("Apartment_ID")
private String apartmentId;
#JsonProperty("Area_Name")
private String areaName;
//constructors along with getters and setters
}
ApartmentSearchRepository.java - This is an interface that extends the ElasticsearchRepository interface to perform crud operations.
public interface ApartmentSearchRepository extends ElasticsearchRepository<Apartments, String> {
List<Apartments> findByApartmentName(String apartmentName);
}
EsApartmentService.java -
#Service
public class EsApartmentService {
#Autowired
ApartmentSearchRepository apartmentSearchRepository;
public List<Apartments> getApartmentByName(String apartmentName) {
return apartmentSearchRepository.findByApartmentName(apartmentName);
}
}
ApartmentController.java - I have created an endpoint that should give back those 20 documents from elasticsearch. (Also, Apartment is a POJO in my project and Apartments is the data model.)
#Autowired
EsApartmentService esApartmentService;
#GetMapping(path = "/search",produces = "application/json")
public Set<Apartment> searchApartmentByName(
#RequestParam(value = "apartmentName", defaultValue = "") String apartmentName) throws IOException {
List<Apartment> apartments= new ArrayList<>();
esApartmentService.getApartmentByName(apartmentName).forEach(apartment-> {
apartments.add(new Apartment(apartment.getApartmentName(), apartment.getApartmentId(), apartment.getAreaName()));
});
return apartments.stream()
.collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Apartment::getApartmentId))));
}
This code gives back a status of 200 but with an empty response. I tried debugging but it seems that it is unable to read those documents from elasticsearch. I went through a couple of solutions but most of them have set the document data from within the code itself.
I am unable to retrieve those documents by hitting the endpoint I specified in the controller. Can someone let me know what I could be missing out on? Thanks! :)
Edit: The screenshot below shows the query and response in Postman.
As far I know, you are able to use #JsonProperty in order to map the POJO to the query response but you're loosing the ability to use the dynamic finder methods (findBy*) of spring data. The dynamic finders generation of spring data relies on reflection and there is where the field names in your POJO become important.
Would you mind to change the field names of you POJO or in your documents to verify this? Or just define a custom query? There is also a powerfull java api where you can define more complex queries: https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.misc.filter
As mentioned above by #ibexit, I removed #JsonProperty and used the native search query builder in my service. Also, it was not taking Apartment_Name and worked when I gave apartment_Name. (seems like Elasticsearch has case issues so I gave it in Camel Case.)
My changes:
Apartments.java - Removed #JsonProperty
#Document(indexName = "house", type = "apartments")
//#JsonIgnoreProperties(ignoreUnknown=true)
public class Apartments {
#Id
private String id;
//#JsonProperty("apartment_ID")
private String apartment_ID;
//#JsonProperty("Area_Name")
private String area_Name;
//#JsonProperty("Apartment_Name")
private String apartment_Name;
}
EsApartmentService.java -
#Service
public class EsApartmentService {
#Autowired
private ElasticsearchTemplate elasticsearchTemplate;
public List<Apartments> getApartmentByName(String apartmentName) {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(org.elasticsearch.index.query.QueryBuilders
.matchQuery("apartment_Name", apartmentName)).build();
Page<Apartments> sampleEntities =
elasticsearchTemplate.queryForPage(searchQuery,Apartments.class);
return sampleEntities.getContent();
}
}
Removed ApartmentSearchRepository.java file.
These changes gave me the required response! :)
My Spring MVC Web Service code is as follows.
Model Class
#XmlRootElement(name="wrappedSecretData")
public class VendorData {
private long lKeyId;
#XmlElement(name="keyId")
public long getlKeyId() {
return lKeyId;
}
public void setlKeyId(long lKeyId) {
this.lKeyId = lKeyId;
}
}
Controller Method
#RequestMapping(value = "/vendor", method = RequestMethod.POST)
public String addVendor(#RequestBody VendorData vendorData) {
/*Checking recieved value*/
System.out.println(vendorData.getlKeyId());//**Returning 0 value **
return "Success";
}
Xml request body for web service
<wrappedVendorSecretsMetadata>
<keyId>1</keyId>
</wrappedVendorSecretsMetadata>
I am getting "0" value in lKeyId(Bold comment).
Where am I doing wrong.
Please provide the correct way to bind the xml element to object member using #XmlElement(name="keyId") annotation.
I think you need the #XmlElement only over the variable declaration.
try this:
#XmlRootElement(name="wrappedVendorSecretsMetadata")
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
public class VendorData {
private long lKeyId;
public VendorData(){
}
#XmlElement(name="keyId")
public long getlKeyId() {
return lKeyId;
}
public void setlKeyId(long lKeyId) {
this.lKeyId = lKeyId;
}
}
By default, annotations doesn't work with XmlMapper in jaxb. You have to register the annotation module for this purpose as I have done in the following code block:
String xmlData = getMyXmlData();
ObjectMapper objectMapper = new XmlMapper();
objectMapper.registerModule(new JaxbAnnotationModule());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MyClass myObj= objectMapper.readValue(xmlData , MyClass.class);
In your case, you have to overwrite the Xml to Object binding process. To do that, you can receive the the HttpRequest in your controller and then convert the xml data to VendorData using your own java code.
I am using RetrofitJackson2SpiceService to make requests in a service. Jackson is used to parse JSON responses from the API.
But I have one problem.
My User model has following declaration
#JsonIgnoreProperties(ignoreUnknown=true)
public class User {
#JsonProperty("id")
public int id;
#JsonProperty("name")
public String name;
#JsonProperty("surname")
public String surname;
#JsonProperty("email")
public String email;
#JsonProperty("phone")
public String phone;
#JsonProperty("BirthDate")
public String birthDate;
#JsonProperty("token_model")
public Token token;
}
As you may noticed this class has Token as a member
#JsonIgnoreProperties(ignoreUnknown=true)
public class Token {
#JsonProperty("token")
public String token;
#JsonProperty("expiration_time")
public int expirationTime;
#JsonProperty("scope")
public int scope;
}
Server response looks like this
{"id":"36","email":"something#yo.com","name":"Say","surname":"hello","login":"superuser","phone":"4534333","token_model":{"token":"a220249b55eb700c27de780d040dea28","expiration_time":"1444673209","scope":"0"}}
Token is not being parsed, it is always null.
I have tried to convert string manually
String json = "{\"id\":\"36\",\"email\":\"something#yo.com\",\"name\":\"Say\",\"surname\":\"hello\",\"login\":\"superuser\",\"phone\":\"4534333\",\"token_model\":{\"token\":\"a220249b55eb700c27de780d040dea28\",\"expiration_time\":\"1444673209\",\"scope\":\"0\"}}";
ObjectMapper mapper = new ObjectMapper();
User user = null;
try {
user = mapper.readValue(json, User.class);
} catch (IOException e) {
e.printStackTrace();
}
And it works ! Token is parsed correctly without any problems.
Here I have used readValue method accepting String as first parameter, but in Converter
JavaType javaType = objectMapper.getTypeFactory().constructType(type);
return objectMapper.readValue(body.in(), javaType);
Stream version of method is used.
I have tried to return Response instead of User object in the following way
public void onRequestSuccess(Response response) {
super.onRequestSuccess(response);
ObjectMapper objectMapper = new ObjectMapper();
User user = null;
try {
user = objectMapper.readValue(response.getBody().in(), User.class);
} catch (IOException e) {
e.printStackTrace();
}
}
And it works great, like it should, token is parsed correctly.
I have no idea what can cause such problem, I have tried a lot of different combinations of annotations(custom deserializers, unwrap....), custom converters but still the same.
I would be grateful for any help.
Thanks.
I have found the problem exploring source code of Retrofit
The problem is that even if my service is inherited from RetrofitJackson2SpiceService it doesn't use JacksonConverter by default.
GsonConverter used instead.
mRestAdapterBuilder = new RestAdapter.Builder()
.setEndpoint(getServerUrl())
.setConverter(createConverter()) //this line
.setRequestInterceptor(new AuthRequestInterceptor(context))
.setClient(new OkClient(mHttpClient))
.setLogLevel(RestAdapter.LogLevel.FULL)
.setLog(new AndroidLog("RETROFIT"));
Adding converter explicitly while building rest adapter solved the problem.
I'm building a RESTful API and want to provide developers with the option to choose which fields to return in the JSON response. This blog post shows examples of how several API's (Google, Facebook, LinkedIn) allow developers to customize the response. This is referred to as partial response.
An example might look like this:
/users/123?fields=userId,fullname,title
In the example above the API should return the userId, fullName and title fields for User "123".
I'm looking for ideas of how to implement this in my RESTful web service. I'm currently using CXF (edit: and Jackson) but willing to try another JAX-RS implementation.
Here's what I currently have. It returns a full User object. How can I return only the fields the API caller wants at runtime based on the "fields" paramaeter? I don't want to make the other fields Null. I simply don't want to return them.
#GET
#Path("/{userId}")
#Produces("application/json")
public User getUser(#PathParam("userId") Long userId,
#DefaultValue("userId,fullname,title") #QueryParam("fields") String fields) {
User user = userService.findOne(userId);
StringTokenizer st = new StringTokenizer(fields, ",");
while (st.hasMoreTokens()) {
// here's where i would like to select only the fields i want to return
}
return user;
}
UPDATE:
I followed unludo's link which then linked to this: http://wiki.fasterxml.com/JacksonFeatureJsonFilter
With that info I added #JsonFilter("myFilter") to my domain class. Then I modified my RESTful service method to return String instead of User as follows:
#GET
#Path("/{userId}")
#Produces("application/json")
public String getUser(#PathParam("userId") Long userId,
#DefaultValue("userId,fullname,title") #QueryParam("fields") String fields) {
User user = userService.findOne(userId);
StringTokenizer st = new StringTokenizer(fields, ",");
Set<String> filterProperties = new HashSet<String>();
while (st.hasMoreTokens()) {
filterProperties.add(st.nextToken());
}
ObjectMapper mapper = new ObjectMapper();
FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter",
SimpleBeanPropertyFilter.filterOutAllExcept(filterProperties));
try {
String json = mapper.filteredWriter(filters).writeValueAsString(user);
return json;
} catch (IOException e) {
e.printStackTrace();
return e.getMessage();
}
}
I need to do more testing but so far so good.
If you use Jackson (a great JSON lib - kind of the standard for Java I believe), you may use the #View annotation to filter what you want in the resulting object.
I understand that you want something dynamic so it's a bit more complicated. You will find what you are looking for here: http://www.cowtowncoder.com/blog/archives/2011/02/entry_443.html (look at 6. Fully dynamic filtering: #JsonFilter).
I would be interested in the solution you will find.
Creating an ObjectMapper instance inside the resource method for every request can have significant performance overhead. According to the Jackson performance best practices object mappers are expensive to create.
Instead you can customize the JAX-RS provider's Jackson object writer inside the resource method using the Jackson 2.3 ObjectWriterModifier/ObjectReaderModifier feature.
Here is an example shows how to register an ObjectWriterModifier thread local object that changes the set of the filters applied for the JAX-RS Jackson provider being used inside a resource method. Note that I have not tested the code against an JAX-RS implementation.
public class JacksonObjectWriterModifier2 {
private static class FilterModifier extends ObjectWriterModifier {
private final FilterProvider provider;
private FilterModifier(FilterProvider provider) {
this.provider = provider;
}
#Override
public ObjectWriter modify(EndpointConfigBase<?> endpoint, MultivaluedMap<String, Object> responseHeaders,
Object valueToWrite, ObjectWriter w, JsonGenerator g) throws IOException {
return w.with(provider);
}
}
#JsonFilter("filter1")
public static class Bean {
public final String field1;
public final String field2;
public Bean(String field1, String field2) {
this.field1 = field1;
this.field2 = field2;
}
}
public static void main(String[] args) throws IOException {
Bean b = new Bean("a", "b");
JacksonJsonProvider provider = new JacksonJsonProvider();
ObjectWriterInjector.set(new FilterModifier(new SimpleFilterProvider().addFilter("filter1",
SimpleBeanPropertyFilter.filterOutAllExcept("field1"))));
provider.writeTo(b, Bean.class, null, null, MediaType.APPLICATION_JSON_TYPE, null, System.out);
}
}
Output:
{"field1":"a"}
The Library jersey-entity-filtering Can do that :
https://github.com/jersey/jersey/tree/2.22.2/examples/entity-filtering-selectable
https://jersey.java.net/documentation/latest/entity-filtering.html
Exemple :
My Object
public class Address {
private String streetAddress;
private String region;
private PhoneNumber phoneNumber;
}
URL
people/1234?select=streetAddress,region
RETURN
{
"streetAddress": "2 square Tyson",
"region": "Texas"
}
Add to Maven
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-entity-filtering</artifactId>
<version>2.22.2</version>
</dependency>