Is it possible to dynamically customize the key names in the JSON response at runtime, rather than creating individual POJO classes for domain level objects?
I am using Spring Boot 1.5.3 with Web Starter, so Jackson dependency is included. I am returning responses in JSON. Typically, I create individual POJO classes, with Jackson annotations if I need to customize key names. For example,
public class Movies {
private List<String> movies;
public Movies(List<String> movies) {
this.movies = movies;
}
public List<String> getMovies() {
return this.movies;
}
public void setMovies(List<String> movies) {
this.movies = movies;
}
}
When I am returning this from a #RestController with the following code:
#RestController
public class MoviesController {
#Service
private MovieService movieService;
#RequestMapping("/movies/list")
public ResponseEntity<Movies> getMovies() {
return new ResponseEntity<Movies>(this.movieService.getMovies(), HttpStatus.OK);
}
}
I get back a JSON response when invoking this end-point:
{ "movies" : [ "Iron Man", "Spiderman", "The Avengers", "Captain America" ] }
I don't want to be creating the Movies POJO. Instead, I would like to have a generic-typed POJO:
public class GenericResponse {
#JsonProperty("movies") // <- this needs to be dynamic
private List<String> data;
...
}
...where I can somehow send any key name I want while instantiating GenericResponse as opposed to hard-coding the key name via a #JsonProperty annotation. Is that possible?
Replace Movies and GenericResponse with Map<String, List<String>>, then do
map.put("movies", Arrays.asList("Iron Man", "Spiderman", "The Avengers", "Captain America"));
A Map is serialized to JSON as a JSON Object, with the map keys as field names, and map values and field values.
What about doing this through Map ?
public class GenericResponse {
#JsonValue
private Map<String, List<String>> data;
}
and you can use #JsonValue annotation to ignore the "data" field name !
Related
I want to filter out some fields in the response. Filtering should be done before the Java object is serialised into the JSON.
Consider:
public class Entity {
#JsonProperty("some_property")
String someProperty;
#JsonProperty("nested_entity")
#OneToMany(mappedBy = "entity", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
NestedEntity nestedEntity;
// other fields for eg fieldA, fieldB
}
API endpoint
get api/entity/{id}?fields=some_property,field_a
Now the ask is, in the o/p we should filter out only someProperty and fieldA. Like
{
"some_property": "foo",
"field_a": "bar"
}
But since these are JSON fields not Java object fields I can't filter or get these fields them by Reflection. Is there a way we can achieve this, i.e. filtering of Java object based on json fields ?
FYI: The advantage of filtering before serialization is that the lazy-fields' DB calls are saved unless these fields are required
Thanks in advance!
On the suggestion of #robocode using #JsonFilter and also to support empty fields or no fields filtering added JacksonConfiguration
#JsonFilter("entityFilter")
public class Entity {
#JsonProperty("some_property")
String someProperty;
// other fields for eg fieldA, fieldB
}
#Configuration
public class JacksonConfiguration {
public JacksonConfiguration(ObjectMapper objectMapper) {
objectMapper.setFilterProvider(new SimpleFilterProvider().setFailOnUnknownId(false));
}
}
public class FieldMapper {
#SneakyThrows
public static Dto getFilteredFields(Dto make, String fields[]) {
ObjectMapper objectMapper = new ObjectMapper();
if(ArrayUtils.isNotEmpty(fields)) {
FilterProvider filters = new SimpleFilterProvider().addFilter(
"entityFilter", SimpleBeanPropertyFilter.filterOutAllExcept(fields)
);
objectMapper.setFilterProvider(filters);
} else {
objectMapper.setFilterProvider(new SimpleFilterProvider().setFailOnUnknownId(false));
}
JsonNode j = objectMapper.readTree(objectMapper.writeValueAsString(make));
return objectMapper.convertValue(j, Dto.class);
}
}
// controller code to get the dto for api/entity/{id}?fields=some_property,field_a
Dto filteredFields = getFilteredFields(dto, fields);
return filteredFields
I am trying to save an object to my database through a REST .POST and when I am receiving the JSON object from the frontend one of the values from a json key has multiple values in an array form.
JSON:
{ "supportedId": [ 2, 4, 1, 18592, 18594 ], "reportSubscriptionId": 100 }
I want to save each "supportedId" as its own INSERT/Unique entry in the database with "reportSubscriptionId: 100" like the json object above.
How do I iterate through the array and save this properly? Any help would be appreciated, thanks!
#RequestMapping(
method = RequestMethod.POST
)
#ResponseBody
public ApplicationUserSubscription createAppSubscription(#RequestBody ApplicationUserSubscription appUser) {
return applicationUserSubscriptionRepository.save(appUser);
#Repository
public interface ApplicationUserSubscriptionRepository extends
JpaRepository<ApplicationUserSubscription, Integer> {}
Create request class to map input and the create entity by processing input request as below,
Entity:
public class ApplicationUserSubscription {
#Id
private Integer supportedId;
private Integer reportSubscriptionId;
//constructor getter setters
}
Request:
class ApplicationUserSubscriptionRequest {
private List<Integer> supportedIds;
private Integer reportSubscriptionId;
//contructor getter setters
}
Controller:
#RequestMapping(method = RequestMethod.POST)
public List<ApplicationUserSubscription> createAppSubscription(#RequestBody ApplicationUserSubscriptionRequest applicationUserSubscriptionRequest) {
List<ApplicationUserSubscription> subscriptions = applicationUserSubscriptionRequest.getSupportedIds().stream()
.map(ele -> new ApplicationUserSubscription(ele, applicationUserSubscriptionRequest.getReportSubscriptionId()))
.collect(Collectors.toList());
return applicationUserSubscriptionRepository.saveAll(subscriptions);
}
Repository:
#Repository
public interface ApplicationUserSubscriptionRepository extends JpaRepository<ApplicationUserSubscription, Integer> {
}
Hope it helps.
I use external application which expects an Object that Serializable from me like his function:
externalFunction(Object input);
So I should give that function an input that will be correctly serialized into JSON when the method is invoked (not controlled by me).
But I don't know how data is structured since I receive input from another external application dynamically. So case like this:
1. Get data from 3rd party
2. MyApp should annotate data for Json Serialization
3. Send data to 3rd party as input
4. Response will be produced as JSON
How can I achieve this? How can I give input to the function that is correctly serialized when the function is invoked?
What I tried so far:
So first thing I try is wrap data with some Wrapper like:
public class JsonWrapper<T> implements Serializable
{
public T attributes;
public JsonWrapper( T attributes )
{
this.attributes = attributes;
}
#JsonValue
public T getAttributes( )
{
return attributes;
}
}
So I wrap data like ->
data = getFromThirdParty();
wrapped = new JsonWrapper<>(data);
externalFunction(wrapped);
But it produces a response with "attributes" field which I don't want. Also I tried to use #JsonUnwrapped public T attributes; but the result is same.
I don't want this:
{
"attributes": {
... some fields/values that I don't know, get from 3rd party
}
}
I want like this:
{
... some fields/values that I don't know, get from 3rd party
}
The #JsonUnwrapped annotation doesn't work when T is a Collection (see this answer from the Jackson's creator). But the #JsonValue annotation actually does the trick:
public class JsonWrapper<T> {
#JsonValue
private T value;
public JsonWrapper(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
If you use Lombok, you can have:
#Getter
#AllArgsConstructor
public class JsonWrapper<T> {
#JsonValue
private T value;
}
Example
Consider the following class:
#Data
#AllArgsConstructor
public class Person {
private String firstName;
private String lastName;
}
When serializing an Person instance, the following result JSON is produced:
ObjectMapper mapper = new ObjectMapper();
JsonWrapper<?> wrapper = new JsonWrapper<>(new Person("John", "Doe"));
String json = mapper.writeValueAsString(wrapper);
{"firstName":"John","lastName":"Doe"}
When serializing a list of Person instances, the following result JSON is produced:
ObjectMapper mapper = new ObjectMapper();
JsonWrapper<?> wrapper = new JsonWrapper<>(
Arrays.asList(
new Person("John", "Doe"),
new Person("Jane", "Poe")
));
String json = mapper.writeValueAsString(wrapper);
[{"firstName":"John","lastName":"Doe"},{"firstName":"Jane","lastName":"Poe"}]
I need to write a JSON string that follows this basic format:
"gesamtAngebot":{
"angebotList":[{
"instanzId":"string",
"buchungsKontextList":[{
"quellSystem":"SOMETHING",
"payload":{}
}],
"payload":{"test1":"test1"}
}]
}
I'm using the following class to present the data, and an instance of this class is serialized with the Jackson ObjectMapper.
#Data
public class Angebot {
private String instanzId;
private List<BuchungsKontext> buchungsKontextList;
private Map<String, Object> payload = new HashMap<String, Object>();
#JsonAnyGetter
public Map<String, Object> any() {
return payload;
}
#JsonAnySetter
public void set(String name, Object value) {
payload.put(name, value);
}
}
If I serialize an instance of this class as-is, the resulting JSON will be something like this:
"gesamtAngebot":{
"angebotList":[{
"instanzId":"string",
"buchungsKontextList":[{
"quellSystem":"SOMETHING",
"payload":{}
}],
"payload":{"test1":"test1"},
"test1":"test1"
}]
}
As you can see the data of "payload" is doubled as it's own element and I don't have any idea why.
Thanks in advance for your attention and advice.
It seems like you want to serialize payload as a normal map. So if you don't want it there twice then you should not have the any() method, just have a regular getter method for payload.
The any() method can be used in case you want to serialize all the items from the payload map to appear like they are properties of Angebot class. Then you would use the any method, and not have a getter for payload.
Your JSON would come out like this:
"gesamtAngebot":{
"angebotList":[{
"instanzId":"string",
"buchungsKontextList":[{
"quellSystem":"SOMETHING",
"payload":{}
}],
"test1":"test1"
}]
}
And it will look like test1 is a variable of the Angebot class.
It is because of any() getter. Just remove it:
#Data
public class Angebot {
private String instanzId;
private List<BuchungsKontext> buchungsKontextList;
private Map<String, Object> payload = new HashMap<String, Object>();
// #JsonAnyGetter
// public Map<String, Object> any() {
// return payload;
// }
#JsonAnySetter
public void set(String name, Object value) {
payload.put(name, value);
}
}
payload is class property. It gets naturally de-serialized because of #Data annotation. any() getter creates duplicity.
My class looks like:
Class A{
private String amount;
#JsonIgnore
private Map<String,String> unknownFields = new HashMap<>();
}
My ObjectMapper have DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES = false configured.
Json input:
{
"amount": 5000,
"note" : "Amount is 5000"
}
In this case I need the note to be in the unknownFields Map:
I am looking for some annotations like
#OnUnknownProperties
public void OnUnknownProperties(String name, String value){
unknownFields.put(name,value);
}
You could annotate a Method in your Domain-Class with #JsonAnySetter (#JsonAnyGetter) and handle it. A good example is here:
http://www.jasonwhaley.com/handling-top-level-metadata-with-jackson/ . Let your DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES=false.