Is there annotation for json-b that works like #JsonValue - java

I'm having a trouble with deserializing JSON like:
[{
"foo": "bar",
"example": "value"
}]
For some reason, API I use packs it in unnamed Array with one element. I'm accessing that API with RestController.
As long as I used default Jackson Converter, I could easily prepare model classes like that (getters, setters, constructors omitted for clarity):
public class Model {
private String foo;
private String example;
}
public class Response {
#JsonValue
private List<Model> model;
}
Repository class
public class FooRepository {
private RestTemplate restTemplate;
public String getFoo() {
ResponseEntity<Response> response =
restTemplate
.exchange("http://localhost:8080/api"
HttpMethod.GET,
new HttpEntity<>(new HttpHeaders()),
Response.class);
return response.getBody().getModel().get(0).getFoo();
}
I cannot find any annotation that works like #JsonValue for Jsonb. Is there one? Or maybe there is another way to fix my problem without using Jackson?
edit:
I figured out a workaround solution.
public String getFoo() {
ResponseEntity<List<Model>> response =
restTemplate
.exchange("http://localhost:8080/api"
HttpMethod.GET,
new HttpEntity<>(new HttpHeaders()),
ParameterizedTypeReference<List<Model>>());
return response.getBody().get(0).getFoo();
}

Related

How to make json deserialization fail when detecting duplicate properties in API POST request body

I have a POST endpoint which accepts a JSON as request body.
#Path("/drink")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class DrinkResource {
#POST
public Drink getDrink(Fruit fruit) {
return new Drink(fruit.getName());
}
}
The request body is supposed to be deserialized into this POJO :
public class Fruit {
private final String name;
#JsonCreator
public Fruit(#JsonProperty("name") String name) {
this.name = name;
}
public String getName() {
return name;
}
}
I'm using Jackson for the deserialization.
Is it possible to make the deserialization fail when the JSON in the request body has duplicate keys ?
For example, if the request body looks like this : {"name" : "banana", "name" : "orange"}, I would like to get a 500 status code or another kind of error instead of having the json deserialized with the last property.
Basically, I'm looking for a solution with the same logic as the JsonParser.Feature.STRICT_DUPLICATE_DETECTION with the ObjectMapper but for a POST endpoint.
I'm also using quarkus so I don't know if there is a property for this. Something similar to quarkus.jackson.fail-on-unknown-properties=true but for the duplicate properties.
Add the following:
#Singleton
public class MyCustomizer implements ObjectMapperCustomizer {
#Override
public void customize(ObjectMapper objectMapper) {
objectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
}
}
If you do, the ObjectMapper that Quarkus uses will throw a JsonParseException thus leading to a HTTP 400 response.

RestTemplate | Reading unknown ENUM values as default value

I'm using org.springframework:spring-web:5.1.9.RELEASE and have a plain (not customized) RestTemplate class object I use to send a POST request by utilizing the EXCHANGE method. I want to allow it desirialize unknown ENUM values from the response to their default value (as set using #JsonEnumDefaultValue) instead of failing the whole operation. I've searched and didn't find any easy way to do it, can you give me some help with that? Thanks!
#Service
#CommonsLog
public class TMServerExternalApiRepositoryImpl implements TMServerExternalApiRepository {
private final RestTemplate restRelay;
private TeleMessageProperties tmUrls;
#Autowired
public TMServerExternalApiRepositoryImpl(RestTemplate restTemplate, TeleMessageProperties tmProperties) {
this.restRelay = restTemplate;
this.tmUrls = tmProperties;
}
#Override
public VnvUsersSearchResult getUsersByPhone(String to, String from) {
log.info(String.format("Trying to get users with telephones to: [%s], from: [%s] ", to, from));
return sendRequest(new VnvUserSearchParams(from, to), VnvUsersSearchResult.class, tmUrls.getGetUserByPhoneUrl()).getBody();
}
//the actual post
private <T, K> ResponseEntity<T> sendRequest(K content, Class<T> returnTypeClass, String url) throws HttpClientErrorException {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
HttpEntity<K> httpEntity = new HttpEntity<>(content, httpHeaders);
return restRelay.exchange(url, HttpMethod.POST, httpEntity, returnTypeClass);
}
}
This is the exception I am getting now:
org.springframework.web.client.RestClientException: Error while extracting response for type [class voiceAndVideo.services.VnvUsersSearchResult] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `voiceAndVideo.Util.VNVDevice$Type` from String "BAD_VALUE": value not one of declared Enum instance names: [MOBILE, ...]; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `voiceAndVideo.Util.VNVDevice$Type` from String "BAD_VALUE": value not one of declared Enum instance names: [MOBILE, ...]
at [Source: (ByteArrayInputStream); line: 1, column: 261] (through reference chain: voiceAndVideo.services.VnvUsersSearchResult["userFrom"]->voiceAndVideo.Util.VNVUser["devices"]->java.util.ArrayList[2]->voiceAndVideo.Util.VNVDevice["type"])
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:117) ~[spring-web-5.1.9.RELEASE.jar:5.1.9.RELEASE]
Classes:
public class VnvUsersSearchResult {
private VNVUser userFrom;
...
}
public class VNVUser {
...
protected List<VNVDevice> devices;
...
}
public class VNVDevice {
public Type type;
String mobile;
public enum Type {
#JsonEnumDefaultValue
UNKNOWN(0),
MOBILE(10),
BUSINESS_PHONE(20),
...
int ID;
Type(int i) {
this.ID = i;
}
}
}
You probably need to configure a custom ObjectMapper to the RestTemplate that you are using. You need to enable this feature on the ObjectMapper. Please make sure you are using fasterxml as the package for all these.
In order to configure one, create a JavaConfig file like this:
#Bean
public RestOperations restOperations() {
RestTemplate rest = new RestTemplate();
rest.getMessageConverters().add(0, mappingJacksonHttpMessageConverter());
return rest;
}
#Bean
public MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter() {
MappingJacksonHttpMessageConverter converter = new MappingJacksonHttpMessageConverter();
converter.setObjectMapper(myObjectMapper());
return converter;
}
#Bean
public ObjectMapper myObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
// This where you enable default enum feature
objectMapper.configure(DeserializationFeature. READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, true);
return objectMapper;
}
I was able to solve it using JsonCreator placed in the ENUM class.
#JsonCreator
public static Type getByValue(String t) {
return Arrays.stream(Type.values())
.filter(a -> a.name().equals(t)).findFirst().orElse(Type.UNKNOWN);
}
}

How to change declared body type from String to custom DTO type in Swagger using SpringBoot and SpringFox

I have a rest controller with one method. This method takes one String argument annotated as #RequestBody. For some reason not mentioned here, I'm forced to use type String and manually convert it to TestDTO. From the API's consumer point of view body is type of TestDTO and I want to show this type in SwaggerUI.
Unfortunately (which is quite obvious) swagger shows that body is type of String. Look at the picture below.
What I want to achieve is to have String body in java code and TestDTO in swagger code. How can I force Swagger to show it? I tried to find annotations and its properties, but failed.
Rest controller code below:
#RestController
#Api(tags = { "test" }, description = "test related resources")
public class TestController {
#Autowired
ObjectMapper mapper;
#RequestMapping(path = "/test", method = RequestMethod.POST)
public void confirm(#RequestBody String requestBody) throws IOException {
//do sth with body
TestDTO dto = mapper.readValue(requestBody, TestDTO.class);
//do sth with dto
}
}
class TestDTO{
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
I figured it out. Two changes need to be made.
First, like in #Dave Pateral's answer #ApiImplicitParams must be added
#RestController
#Api(tags = { "test" }, description = "test related resources")
public class TestController {
#Autowired
ObjectMapper mapper;
#ApiImplicitParams({
#ApiImplicitParam(name = "requestBody", required = true,
dataType = "TestDTO", paramType = "body")
})
#RequestMapping(path = "/test", method = RequestMethod.POST)
public void confirm(#RequestBody String requestBody) throws IOException {
//do sth with body
TestDTO dto = mapper.readValue(requestBody, TestDTO.class);
//do sth with dto
}
}
And then implicit Model must be registered in the docket, minimal working example below
#Configuration
public class SwaggerConfiguration {
#Autowired
private TypeResolver typeResolver;
#Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.additionalModels(typeResolver.resolve(TestDTO.class));
}
}
And the result is
Try put this annotation on your method:
#ApiImplicitParam(name = "test", value = "testDTO", required = true, dataType = "TestDTO")

how can i consume JSON response with many JsonProperty name in java

i'm trying to consume a Json response using RestTemplate in java with Jackson annotations, and i have a resource that have many name properties like so:
{
-name1:{
id:2,
price:12,
name:"Bob:
},
-name2:{
id:111,
price:1.1,
name:"Ron:
},
-name3:{
id:10,
price:33,
name:"jhon:
},
}
and the list go on like this.
this is my code of how to get one of the entities, like name1 object:
public class Class1 {
private RestTemplate restTemplate;
private String url = "https://url.com/api";
private Response response;
private Market market ;
public class1(){
restTemplate = new RestTemplate();
response = restTemplate.getForObject(url,Response.class);
}
#Override
public Market getResults() {
market = response.getResult();
System.out.println(Market);
return null;
}
}
and the response class is like so :
#JsonIgnoreProperties(ignoreUnknown = true)
#Getter
#Setter
#NoArgsConstructor
public class Response {
#JsonProperty("name1")
private Market result;
}
how can i get all those elements as an array or ArrayList?
this API is from 3rd party website and there's many entities liek that in the Json response.
thanks in advance.
So in the Json above, it is not an array but a list of key value pair.
This is what an array looks like in Json:
{
marketResults: [
{
id:2,
price:12,
name:"Bob:
},
{
id:111,
price:1.1,
name:"Ron:
},
{
id:10,
price:33,
name:"jhon:
}
]
}
Then what you could have done is:
public class Response {
private List<Market> marketResults;
}
But since your example is a map, you need to to use a MAP
public class Response {
private Map<String, Object > marketResults;
}
That post actually similar to yours: Reading JSON map structure via spring boot
If you can use Gson library that has native support for this use-case.
Keeps your code clean and typed.
#Getter
#Setter
public class Response {
#SerializedName(value = "name1", alternate={"name2","name3"})
private Market result;
}
#SerializedName is the #JsonProperty equivalent in Gson.

Spring: RestTemplate returns null object

With the below GET request:
ResponseEntity<String> entity = restTemplate.exchange(uri, HttpMethod.GET, requestEntity, String.class );
entity.getBody();
returns a JSON String like this:
{"userRegistrations":[{"userRegistrationToken":"fb398972","userRegistrationTokenAlias":"87f15f8"}]}
But I want to make this work with an object not with a string. So with the code below I receive a UserRegistrations object with a null UserTokenResponse List
ResponseEntity<UserRegistrations> entity = restTemplate.exchange(uri, HttpMethod.GET, requestEntity, UserRegistrations.class );
entity.getBody();
And my domain class looks like this:
public class UserRegistrations {
List<UserTokenResponse> userRegistrationList;
//..getters and setters
}
public class UserTokenResponse {
private String userRegistrationToken;
private String userRegistrationTokenAlias;
//getters and setters
}
What am I missing?
Assuming you're using Jackson, RestTemplate automatically registers a MappingJackson2HttpMessageConverter which configures the underlying ObjectMapper to ignore unknown properties.
The JSON object has a single attribute named userRegistrations, whereas your Java class has a single attribute named userRegistrationList. They don't match.
They need to match, or you need to add a #JsonProperty annotation of the attribute to make Jackson serialize/parse it as userRegistrations.
This happens when your class property names doesn't match with the JSON property names coming in the response. For instance take the below example
public class ScheduledCallbacks {
private List<Callback> callbacks;
public List<Callback> getCallbacks() {
return callbacks;
}
public void setCallbacks(List<Callback> callbacks) {
this.callbacks = callbacks;
}
#Override
public String toString() {
return "ScheduledCallbacks [callbacks=" + callbacks + "]";
}
}
and if the response is the following way
{
"scheduledCallbacks": [
{
"sessionId": "string",
"customerNbr": "string",
"topicName": "string",
"desiredTime": "string",
"callbackState": "string",
"serviceName": "string",
"expirationTime": "string",
"programCode": "string"
}
]
}
Then you get null response because the name scheduledCallbacks in the JSON response doesn't match with the name callbacks in class.
But if your class is as following
public class ScheduledCallbacks {
private List<Callback> scheduledCallbacks;
public List<Callback> getScheduledCallbacks() {
return scheduledCallbacks;
}
public void setScheduledCallbacks(List<Callback> scheduledCallbacks) {
this.scheduledCallbacks = scheduledCallbacks;
}
#Override
public String toString() {
return "ScheduledCallbacks [scheduledCallbacks=" + scheduledCallbacks + "]";
}
}
Then you get the expected response in response entity
I encountered a similar error and it was returning null too. The problem is over when Object.class is replaced with the name of the class we want to convert on the client side.
Like that:
Token = restTemplate.exchange(uri, HttpMethod.POST, request, Object.class);
the problem was probably due to the fact that it is not directly compatible with the class we want to convert.

Categories