I have a POJO and I am using Jackson to deserialize it into the POJO.I want to log key value attributes which I'm not aware of. For this I use JsonAnySetter in the following manner:
#Value
#Slf4j
#Builder
public class Book {
private String titleId;
private String bookName;
private List<String> authors;
#JsonAnySetter
public void ignored(String key, Object value) {
log.warn("Received a key which hasn't been mapped. Key: {}, Value: {}", key, value);
}
}
Now to test this in my Junit how do I verify that the method ignored is called?
#Test
public void given_unknown_key_ensure_it_is_logged() {
ObjectMapper objectMapper = new ObjectMapper();
String test = "{\"randomField\": \"test\"}";
Book book = objectMapper.readValue(test, Book.class);
ArgumentCaptor<Book> captor = ArgumentCaptor.forClass(Book.class);
verify(book, times(1)).ignored("randomField", "test");
}
I need to use the actual Book object, but that has to be mocked as well, so not sure how I should proceed in this case.
Create a mock object of the Log object and set it in the Book class.
This assumes that the Log object is static,
which seems reasonable.
Related
I am trying to update value of one object from another using object mapper readerForUpdating function. But I have a use case where I have to update only null values of my existing object which I am unable to do so. Could anyone please help on this.
Class:
#Getter
#Setter
class Record {
private String source;
private String resource;
}
Record Object:
Record record = new Record();
record.setSource("SourceFromObject");
record.setResource(null);
Object to be Updated:
String incomingJson = "{"source":"SourceFromJson", "resource":"ResourceFromJson"}"
ObjectMapper Logic:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.readerForUpdating(record).readValue(incomingJson);
Am getting the below output
Record: {"source":"SourceFromJson", "resource":"ResourceFromJson"}
But my expected output is
Record: {"source":"SourceFromObject", "resource":"ResourceFromJson"}
I tried several suggestions but didn't worked. I have also tried #JsonMerge annotation.
That's the expected behavior for the ObjectReader returned by ObjectMapper.readerForUpdating(), it would update the given Object with the provided JSON data.
Therefore, you see "SourceFromJson" as the value of the source.
Jackson Databinding
If you want to preserve the value of the field source intact. Then you can annotate it with #JsonIgnore.
Or you can apply #JsonProperty annotation on the source field specifying the access attribute with a value of JsonProperty.Access.READ_ONLY (that would prevent Jackson from writing into this field, but it would be reflected during serialization of the POJO).
Smart Setters
Another option would be to add validation logic into the setter method, to ensure that each gets updated only if it's null (empty, etc. depending on the type and your requirements).
Here's how it might look like:
#Getter
public static class Record {
public static final Predicate<String> NULL_OR_EMPTY =
s -> s == null || s.isEmpty(); // predicate can be reused to validate multiple properties
private String source;
private String resource;
public void setSource(String source) {
if (NULL_OR_EMPTY.test(this.source)) this.source = source;
}
public void setResource(String resource) {
if (NULL_OR_EMPTY.test(this.resource)) this.resource = resource;
}
}
Usage example:
public static void main(String[] args) throws IOException {
String incomingJson = """
{"source":"SourceFromJson", "resource":"ResourceFromJson"}
""";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.readerForUpdating(record).readValue(incomingJson);
System.out.println(record);
String str = "helloslkhellodjladfjhello";
System.out.println(countOccurrences(str, "hello"));
}
Output:
Record{source='SourceFromObject', resource='ResourceFromJson'}
Our company is planning to switch our microservice technology to Spring Boot. As an initiative I did some advanced reading and noting down its potential impact and syntax equivalents. I also started porting the smallest service we had as a side project.
One issue that blocked my progress was trying to convert our Json request/response exchange to Spring Boot.
Here's an example of the code: (This is Nutz framework for those who don't recognize this)
#POST
#At // These two lines are equivalent to #PostMapping("/create")
#AdaptBy(type=JsonAdapter.class)
public Object create(#Param("param_1") String param1, #Param("param_2) int param2) {
MyModel1 myModel1 = new MyModel1(param1);
MyModel2 myModel2 = new MyModel2(param2);
myRepository1.create(myMode12);
myRepository2.create(myModel2);
return new MyJsonResponse();
}
On PostMan or any other REST client I simply pass POST:
{
"param_1" : "test",
"param_2" : 1
}
I got as far as doing this in Spring Boot:
#PostMapping("/create")
public Object create(#RequestParam("param_1") String param1, #RequestParam("param_2) int param2) {
MyModel1 myModel1 = new MyModel1(param1);
MyModel2 myModel2 = new MyModel2(param2);
myRepository1.create(myMode12);
myRepository2.create(myModel2);
return new MyJsonResponse();
}
I am not sure how to do something similar as JsonAdapter here. Spring doesn't recognize the data I passed.
I tried this but based on the examples it expects the Json paramters to be of an Entity's form.
#RequestMapping(path="/wallet", consumes="application/json", produces="application/json")
But I only got it to work if I do something like this:
public Object (#RequestBody MyModel1 model1) {}
My issue with this is that MyModel1 may not necessarily contain the fields/parameters that my json data has.
The very useful thing about Nutz is that if I removed JsonAdapter it behaves like a regular form request endpoint in spring.
I couldn't find an answer here in Stack or if possible I'm calling it differently than what existing spring devs call it.
Our bosses expect us (unrealistically) to implement these changes without forcing front-end developers to adjust to these changes. (Autonomy and all that jazz). If this is unavoidable what would be the sensible explanation for this?
In that case you can use Map class to read input json, like
#PostMapping("/create")
public Object create(#RequestBody Map<String, ?> input) {
sout(input.get("param1")) // cast to String, int, ..
}
I actually figured out a more straightforward solution.
Apparently this works:
#PostMapping("/endpoint")
public Object endpoint(#RequestBody MyWebRequestObject request) {
String value1 = request.getValue_1();
String value2 = request.getValue_2();
}
The json payload is this:
{
"value_1" : "hello",
"value_2" : "world"
}
This works if MyRequestObject is mapped like the json request object like so. Example:
public class MyWebRequestObject {
String value_1;
String value_2
}
Unmapped values are ignored. Spring is smart like that.
I know this is right back where I started but since we introduced a service layer for the rest control to interact with, it made sense to create our own request model object (DTOs) that is separate from the persistence model.
You can use #RequestBody Map as a parameter for #PostMapping, #PutMapping and #PatchMapping. For #GetMapping and #DeleteMapping, you can write a class which implements Converter to convert from json-formed request parameters to Map. And you would register that class as a bean with #Component annotation. Then you can bind your parameters to #RequestParameter Map.
Here is an example of Converter below.
#Component
public class StringToMapConverter implements Converter<String, Map<String, Object>> {
private final ObjectMapper objectMapper;
#Autowired
public StringToMapConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
#Override
public Map<String, Object> convert(String source) {
try {
return objectMapper.readValue(source, new TypeReference<Map<String, Object>>(){});
} catch (IOException e) {
return new HashMap<>();
}
}
}
If you want to exclude specific field of your MyModel1 class, use #JsonIgnore annotation onto the field like below.
class MyModel1 {
private field1;
#JsonIgnore field2;
}
Then, I guess you can just use what you have done.(I'm not sure.)
public Object (#RequestBody MyModel1 model1) {}
i think that you can use a strategy that involve dto
https://auth0.com/blog/automatically-mapping-dto-to-entity-on-spring-boot-apis/
you send a json to your rest api that is map like a dto object, after you can map like an entity or use it for your needs
try this:
Add new annotation JsonParam and implement HandlerMethodArgumentResolver of this, Parse json to map and get data in HandlerMethodArgumentResolver
{
"aaabbcc": "aaa"
}
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonParam {
String value();
}
#Component
public class JsonParamMethodResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(JsonParam.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
RepeatedlyRequestWrapper nativeRequest = webRequest.getNativeRequest(RepeatedlyRequestWrapper.class);
if (nativeRequest == null) {
return null;
}
Gson gson = new Gson();
Map<String, Object> response = gson.fromJson(nativeRequest.getReader(), new TypeToken<Map<String, Object>>() {
}.getType());
if (response == null) {
return null;
}
JsonParam parameterAnnotation = parameter.getParameterAnnotation(JsonParam.class);
String value = parameterAnnotation.value();
Class<?> parameterType = parameter.getParameterType();
return response.get(value);
}
}
#Configuration
public class JsonParamConfig extends WebMvcConfigurerAdapter {
#Autowired
JsonParamMethodResolver jsonParamMethodResolver;
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(jsonParamMethodResolver);
}
}
#PostMapping("/methodName")
public void methodName(#JsonParam("aaabbcc") String ddeeff) {
System.out.println(username);
}
I am trying to de-serialize this JSON object using Jackson 2.8 as part of Retrofit response. Here is the JSON response I get from the server.
{
"id":"8938209912"
"version":"1.1"
"cars":{
"mercedes":[
{
"property":"color"
},
{
"property":"price"
},
{
"property":"location"
}
],
"tesla":[
{
"property":"environment"
}
]
}
}
Based on the query, the cars above may have one or more models returned. I cannot create a class each for each model as these get created/removed arbitrarily. For each model of the car (say tesla), there may be one or more property key-value pairs.
I am new to Jackson. I have been looking at several examples and looks like a custom #JsonDeserialize is the best way to go. So, I created Root class and Cars class like this:
// In file Root.java
public class Root {
#JsonProperty("id")
private String id = null;
#JsonProperty("version")
private String version = null;
#JsonProperty("cars")
private Cars cars = null;
}
// In file Cars.java
public class Cars {
public Cars(){}
#JsonDeserialize(using = CarDeserializer.class)
private Map<String, List<Property>> properties;
public Map<String, List<Property>> getProperties() {
return properties;
}
public void setProperties(Map<String, List<Property>> properties) {
this.properties = properties;
}
}
// Property.java
public class Property {
#JsonProperty("property")
private String property;
}
My de-serializer is below. However, even though the empty constructor gets called, the parse method itself is not called at all!
// CarDeserializer.class
public class RelationshipDeserializer extends StdDeserializer<Map<String, List<Action>>>{
protected RelationshipDeserializer(){
super(Class.class);
}
#Override
public Map<String, List<Action>> deserialize(JsonParser parser, DeserializationContext ctx)
throws IOException, JsonProcessingException
{
// This method never gets invoked.
}
}
My questions:
Is this the right approach in the first place?
Why do you think the execution never gets to the deserialize()? (I checked, the cars object is present in JSON.
Are there better approaches to parse this JSON using Jackson?
The "properties" deserializer is never called because that does not match anything in that JSON. The field name in the JSON is "property" and it does not match Map<String, List<Property>>. It looks like it would be closer to List<Property>
Do you control the in coming JSON? It would be better for the car name/type to be in its own field rather than the name of the object. Then you can use a generic object. What you have now is going to break. Any time they add a new name/type and you do not have a matching object for it.
I have
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
SessionInfo register(UserProfile profileJson){
...
}
I pass profileJson this way:
http://server/url?profileJson={"email": "mymail#gmail.com"}
but my profileJson object has all null fields. What should I do to make spring parse my json?
The solution to this is so easy and simple it will practically make you laugh, but before I even get to it, let me first emphasize that no self-respecting Java developer would ever, and I mean EVER work with JSON without utilizing the Jackson high-performance JSON library.
Jackson is not only a work horse and a defacto JSON library for Java developers, but it also provides a whole suite of API calls that makes JSON integration with Java a piece of cake (you can download Jackson at http://jackson.codehaus.org/).
Now for the answer. Assuming that you have a UserProfile pojo that looks something like this:
public class UserProfile {
private String email;
// etc...
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
// more getters and setters...
}
...then your Spring MVC method to convert a GET parameter name "profileJson" with JSON value of {"email": "mymail#gmail.com"} would look like this in your controller:
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper; // this is your lifesaver right here
//.. your controller class, blah blah blah
#RequestMapping(value="/register", method = RequestMethod.GET)
public SessionInfo register(#RequestParam("profileJson") String profileJson)
throws JsonMappingException, JsonParseException, IOException {
// now simply convert your JSON string into your UserProfile POJO
// using Jackson's ObjectMapper.readValue() method, whose first
// parameter your JSON parameter as String, and the second
// parameter is the POJO class.
UserProfile profile =
new ObjectMapper().readValue(profileJson, UserProfile.class);
System.out.println(profile.getEmail());
// rest of your code goes here.
}
Bam! You're done. I would encourage you to look through the bulk of Jackson API because, as I said, it is a lifesaver. For example, are you returning JSON from your controller at all? If so, all you need to do is include JSON in your lib, and return your POJO and Jackson will AUTOMATICALLY convert it into JSON. You can't get much easier than that. Cheers! :-)
This could be done with a custom editor, that converts the JSON into a UserProfile object:
public class UserProfileEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) throws IllegalArgumentException {
ObjectMapper mapper = new ObjectMapper();
UserProfile value = null;
try {
value = new UserProfile();
JsonNode root = mapper.readTree(text);
value.setEmail(root.path("email").asText());
} catch (IOException e) {
// handle error
}
setValue(value);
}
}
This is for registering the editor in the controller class:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(UserProfile.class, new UserProfileEditor());
}
And this is how to use the editor, to unmarshall the JSONP parameter:
#RequestMapping(value = "/jsonp", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
#ResponseBody
SessionInfo register(#RequestParam("profileJson") UserProfile profileJson){
...
}
You can create your own Converter and let Spring use it automatically where appropriate:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
#Component
class JsonToUserProfileConverter implements Converter<String, UserProfile> {
private final ObjectMapper jsonMapper = new ObjectMapper();
public UserProfile convert(String source) {
return jsonMapper.readValue(source, UserProfile.class);
}
}
As you can see in the following controller method nothing special is needed:
#GetMapping
#ResponseBody
public SessionInfo register(#RequestParam UserProfile userProfile) {
...
}
Spring picks up the converter automatically if you're using component scanning and annotate the converter class with #Component.
Learn more about Spring Converter and type conversions in Spring MVC.
This does solve my immediate issue, but I'm still curious as to how you might pass in multiple JSON objects via an AJAX call.
The best way to do this is to have a wrapper object that contains the two (or multiple) objects you want to pass. You then construct your JSON object as an array of the two objects i.e.
[
{
"name" : "object1",
"prop1" : "foo",
"prop2" : "bar"
},
{
"name" : "object2",
"prop1" : "hello",
"prop2" : "world"
}
]
Then in your controller method you recieve the request body as a single object and extract the two contained objects. i.e:
#RequestMapping(value="/handlePost", method = RequestMethod.POST, consumes = { "application/json" })
public void doPost(#RequestBody WrapperObject wrapperObj) {
Object obj1 = wrapperObj.getObj1;
Object obj2 = wrapperObj.getObj2;
//Do what you want with the objects...
}
The wrapper object would look something like...
public class WrapperObject {
private Object obj1;
private Object obj2;
public Object getObj1() {
return obj1;
}
public void setObj1(Object obj1) {
this.obj1 = obj1;
}
public Object getObj2() {
return obj2;
}
public void setObj2(Object obj2) {
this.obj2 = obj2;
}
}
Just add #RequestBody annotation before this param
i have an class with the following annotations:
class A {
public Map<String,List<String>> references;
#JsonProperty
public Map<String,List<String>> getReferences() {
...
}
#JsonIgnore
public void setReferences(Map<String,List<String>>) {
}
...
}
}
What I try is to ignore the json on deserialization. But it doesn't work. Always when JSON String arrives the Jackson lib fill the references attribute. If I use only the #JsonIgnore annotation the getter doesn't work. Are there any solutions for this problem?
Thanks
I think there are two key pieces that should enable you to have "read-only collections" as desired. First, in addition to ignoring the setter, ensure that your field is also marked with #JsonIgnore:
class A {
#JsonIgnore
public Map<String,List<String>> references;
#JsonProperty
public Map<String,List<String>> getReferences() { ... }
#JsonIgnore
public void setReferences(Map<String,List<String>>) { ... }
}
Second, in order to prevent the getters from being used as setters, disable the USE_GETTERS_AS_SETTERS feature:
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.USE_GETTERS_AS_SETTERS);
As of Jackson 2.6, there is a new and improved way to define read-only and write-only properties, using JsonProperty#access() annotation. This is recommended over use of separate JsonIgnore and JsonProperty annotations.
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
public Map<String,List<String>> references;
You have to make sure there is #JsonIgnore annotation on the field level as well as on the setter, and getter annotated with #JsonProperty.
public class Echo {
#Null
#JsonIgnore
private String doNotDeserialise;
private String echo;
#JsonProperty
public String getDoNotDeserialise() {
return doNotDeserialise;
}
#JsonIgnore
public void setDoNotDeserialise(String doNotDeserialise) {
this.doNotDeserialise = doNotDeserialise;
}
public String getEcho() {
return echo;
}
public void setEcho(String echo) {
this.echo = echo;
}
}
#Controller
public class EchoController {
#ResponseBody
#RequestMapping(value = "/echo", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
public Echo echo(#RequestBody #Valid Echo echo) {
if (StringUtils.isEmpty(echo.getDoNotDeserialise())) {
echo.setDoNotDeserialise("Value is set by the server, not by the client!");
}
return echo;
}
}
If you submit a JSON request with a “doNotDeserialise” value set to something, when JSON is deserialised to an object it will be set to null (if not I put a validation constraint on the field so it will error out)
If you set the “doNotDeserialise” value to something on the server then it will be correctly serialised to JSON and pushed to the client
I used #JsonIgnore on my getter and it didn't work and I couldn't configure the mapper (I was using Jackson Jaxrs providers). This worked for me:
#JsonIgnoreProperties(ignoreUnknown = true, value = { "actorsAsString",
"writersAsString", "directorsAsString", "genresAsString" })
I can only think of a non-jackson solution, to use a base class that does not have references for the mapping and then cast to the actual class:
// expect a B on an incoming request
class B {
// ...
}
// after the data is read, cast to A which will have empty references
class A extends B {
public Map<String,List<String>> references;
}
Why do you even send the References if you don't want them?
Or is the incoming data out of your hands and you just want to avoid the mapping exception telling you that jackson cannot find a property to set for incoming references? For that we use a base class which all of our Json model classes inherit:
public abstract class JsonObject {
#JsonAnySetter
public void handleUnknown(String key, Object value) {
// for us we log an error if we can't map but you can skip that
Log log = LogFactory.getLog(String.class);
log.error("Error mapping object of type: " + this.getClass().getName());
log.error("Could not map key: \"" + key + "\" and value: \"" + "\"" + value.toString() + "\"");
}
Then in the POJO you add #JsonIgnoreProperties so that incoming properties will get forwarded to handleUnknown()
#JsonIgnoreProperties
class A extends JsonObject {
// no references if you don't need them
}
edit
This SO Thread describes how to use Mixins. This might be the solution, if you want to keep your structure exactly as it is, but I have not tried it.