Jackson diffrent name base of other field value - java

I have POJO class like this
class Data {
#JsonAlias({"username", "name"})
String surname;
Type type;
}
enum Type{
PERSON, USER
}
I want serialization of the Data class but when type is PERSON, JSON property surname is name and when type is USER, surname field as the name
Actually, I can create more child classes of Data class but my real type is more than 10 types with different names, and only difference is name of JSON property and do work similar.

Probably the simplest option would be to use com.fasterxml.jackson.annotation.JsonAnyGetter annotation. Create a method which returns Map<String, String> and create pair which meets your condition. Below code shows how it could look like:
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class DataApp {
private final static JsonMapper JSON_MAPPER = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.addModule(new JavaTimeModule())
.build();
public static void main(String[] args) throws Exception {
List<Role> roles = List.of(new Role("John", Type.USER), new Role("Tom", Type.PERSON));
JSON_MAPPER.writeValue(System.out, roles);
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
class Role {
#JsonIgnore
String name;
Type type;
#JsonAnyGetter
private Map<String, String> createDynamicProperties() {
if (Objects.isNull(type)) {
return Map.of();
}
return switch (type) {
case USER -> Map.of("name", name);
case PERSON -> Map.of("surname", name);
};
}
}
enum Type {
PERSON, USER
}
Above code prints:
[ {
"type" : "USER",
"name" : "John"
}, {
"type" : "PERSON",
"surname" : "Tom"
} ]
See also:
How to use dynamic property names for a Json object
Adding a dynamic json property as java pojo for jackson

Related

How to get around error "Unrecognized field "Name, not marked as ignorable", to insert JSON data into H2 database with Spring?

I am trying to insert the following json data into H2 database with Spring by following the process shown in this Dan Vega video on YouTube. But, I get the following error:
Error:
Unable to save products: Unrecognized field "Name" (class com.saurabhsomani.domain.Product), not marked as ignorable (6 known properties: "salesCount", "price", "name", "category", "cust_rating", "id"])
at [Source: (BufferedInputStream); line: 2, column: 12] (through reference chain: java.util.ArrayList[0]->com.saurabhsomani.domain.Product["Name"])
Could you please help me fix this issue? Below are the code details:
My JSON (product.json) looks like:
[{
"Name": "P1",
"ID": 1,
"Price": 970,
"SalesCount": 300,
"Category": "A",
"Cust_Rating": 3.7
},
{
"Name": "P2",
"ID": 2,
"Price": 1170,
"SalesCount": 718,
"Category": "A",
"Cust_Rating": 3.8
},
{
"Name": "P3",
"ID": 3,
"Price": 1090,
"SalesCount": 1253,
"Category": "A",
"Cust_Rating": 0.5
}
]
Project Structure looks like:
Project Structure
JsondbApplication.java
package com.saurabhsomani;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.saurabhsomani.domain.Product;
import com.saurabhsomani.service.ProductService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
#SpringBootApplication
public class JsondbApplication {
public static void main(String[] args) {
SpringApplication.run(JsondbApplication.class, args);
}
#Bean
CommandLineRunner runner(ProductService productService){ //will help us when the application starts up
return args -> { //functional interface
//read json and write to db
ObjectMapper mapper = new ObjectMapper();
//We want a list of products
TypeReference<List<Product>> typeReference = new TypeReference<List<Product>>(){};
InputStream inputStream = TypeReference.class.getResourceAsStream("/json/product.json");
try{
//mapper helps us map json structure to the domain object
List<Product> products = mapper.readValue(inputStream, typeReference);
productService.save(products);
System.out.println("Products Saved!");
} catch (IOException e){
System.out.println("Unable to save products: " + e.getMessage());
}
};
}
}
ProductController.java
package com.saurabhsomani.controller;
import com.saurabhsomani.domain.Product;
import com.saurabhsomani.service.ProductService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/products")
public class ProductController {
//no business logic in controller
private ProductService productService;
//constructor
public ProductController(ProductService productService) {
this.productService = productService;
}
public ProductService getProductService() {
return productService;
}
#GetMapping("/list")
public Iterable<Product> list(){
return productService.list();
}
}
Product.java
package com.saurabhsomani.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Data
#AllArgsConstructor
#Entity
public class Product {
#Id
#GeneratedValue( strategy = GenerationType.AUTO)
private String name;
private int id;
private int price;
private int salesCount;
private String category;
private double cust_rating;
public Product(){
}
}
ProductService.java
package com.saurabhsomani.service;
import com.saurabhsomani.domain.Product;
import com.saurabhsomani.repository.ProductRepository;
import org.springframework.stereotype.Service;
import java.util.List;
#Service
public class ProductService {
private ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public Iterable<Product> list(){
return productRepository.findAll();
}
//to save one product
public Product save(Product product){
return productRepository.save(product);
}
//to save list of products
public void save(List<Product> products) {
productRepository.saveAll(products);
}
}
ProductRepository
package com.saurabhsomani.repository;
import com.saurabhsomani.domain.Product;
import org.springframework.data.repository.CrudRepository;
public interface ProductRepository extends CrudRepository <Product, String>{
}
In Product class you need to add Jackson's #JsonProperty annotation on properties relevant to attributes of source JSON as they are not identical (but differently cased)
public class Product {
...
#JsonProperty("Name")
private String name;
#JsonProperty("Price")
private int price;
#JsonProperty("Cust_Rating")
private double cust_rating;
// and others
}
P.S. try to avoid underscores in names outside of unit tests and stick to Java Code Conventions (https://google.github.io/styleguide/javaguide.html)

Jackson xml 2.9.0: #JacksonXmlElementWrapper not working with #JsonCreator & #JsonProperty constructor

I would like that my ParentClass has final fields, 'brokenChildList' list is wrapped xml element and list items have different tag than the list (<brokenChildList><brokenChild/></brokenChildList>).
Here is a snippet of code to reproduce my issues (imports are partially truncated, setters and getters omitted)
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
public class Main {
public static void main(String... args) throws IOException {
ObjectMapper xmlMapper = new XmlMapper();
String xmlString = "<ParentClass><childClass name=\"name1\" value=\"val1\"/><brokenChildList><brokenChild name=\"bc1\" reason=\"bc-val1\"/><brokenChild name=\"bc2\" reason=\"bc-val2\"/></brokenChildList></ParentClass>";
ParentClass parentClass = xmlMapper.readValue(xmlString, ParentClass.class);
StringWriter stringWriter = new StringWriter();
xmlMapper.writeValue(stringWriter, parentClass);
String serialised = stringWriter.toString();
System.out.println(serialised);
System.out.println(xmlString.equals(serialised));
}
public static class ChildClass {
#JacksonXmlProperty(isAttribute = true)
private String name;
#JacksonXmlProperty(isAttribute = true)
private String value;
//getters & setters
}
public static class BrokenChild {
#JacksonXmlProperty(isAttribute = true)
private String name;
#JacksonXmlProperty(isAttribute = true)
private String reason;
//getters & setters
}
public static class ParentClass {
private final ChildClass childClass;
private final List<BrokenChild> brokenChildList;
#JsonCreator
public ParentClass(
#JsonProperty("childClass") ChildClass childClass,
#JsonProperty("brokenChildList") List<BrokenChild> brokenChildList
) {
this.childClass = childClass;
this.brokenChildList = brokenChildList;
}
#JacksonXmlProperty(localName = "childClass")
public ChildClass getChildClass() {
return childClass;
}
#JacksonXmlElementWrapper(localName = "brokenChildList")
#JacksonXmlProperty(localName = "brokenChild")
public List<BrokenChild> getBrokenChildList() {
return brokenChildList;
}
}
}
The above code gives output with Jackson version 2.8.10:
<ParentClass><childClass name="name1" value="val1"/><brokenChildList><brokenChild name="bc1" reason="bc-val1"/><brokenChild name="bc2" reason="bc-val2"/></brokenChildList></ParentClass>
true
With Jackson version 2.9.0 it gives:
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Duplicate property 'brokenChildList' for [simple type, class org.test.Main$ParentClass]
at [Source: (StringReader); line: 1, column: 1]
I would like to find a solution (and any version after 2.9.0) that will give same output with the attached code.
My failed attempts include:
Replacing #JacksonXmlElementWrapper(localName = "brokenChildList") with #JacksonXmlElementWrapper will rename wrapper element as 'brokenChild' which is undesirable.
Removing #JacksonXmlElementWrapper(localName = "brokenChildList") will rename wrapper element as 'brokenChild' which is undesirable.
This problem is really tricky because Jackson collects metadata from different places: fields, getters, setters, constructor parameters. Also, you can use MixIn but in your case it does not appear.
#JacksonXmlElementWrapper annotation can be attached to FIELD and METHOD type elements and this forces you to declare it on getter. Because ParentClass is immutable and you want to build it with constructor we need to annotate constructor parameters as well. And this is where collision appears: you have a constructor parameter with #JsonProperty("brokenChildList") annotation and getter with #JacksonXmlElementWrapper(localName = "brokenChildList") which reuses the same name. If you would changed localName to #JacksonXmlElementWrapper(localName = "brokenChildListXYZ") (added XYZ) everything would be deserialised and serialised but output would be different then input.
To solve this problem, we can use com.fasterxml.jackson.databind.deser.BeanDeserializerModifier class which allows to filter out fields we do not want to use for deserialisation and which creates collision. Example usage:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
import java.util.stream.Collectors;
public class XmlMapperApp {
public static void main(String... args) throws IOException {
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public List<BeanPropertyDefinition> updateProperties(DeserializationConfig config, BeanDescription beanDesc, List<BeanPropertyDefinition> propDefs) {
if (beanDesc.getBeanClass() == ParentClass.class) {
return propDefs.stream().filter(p -> p.getConstructorParameter() != null).collect(Collectors.toList());
}
return super.updateProperties(config, beanDesc, propDefs);
}
});
XmlMapper xmlMapper = XmlMapper.xmlBuilder()
.addModule(module)
.build();
//yours code
}
}
To create this example I used version 2.10.0.
See also:
Jackson 2.10 features
Jackson Release 2.10

Jackson ignoring specific property but able to check was it availble

Is there a way to skip some properties on deserialization but at the same time knowing are they presented or not?
{
"id": 123,
"name": "My Name",
"picture": {
// a lot of properties that's not important for me
}
}
#JsonIgnoreProperties(ignoreUnknown=true)
#JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
private int id;
}
So, I ignoreUnknown is what I want as a default behavior because I don't want name field and all other fields that can exist. The value of picture fields also is not important. I just want to know was picture property available or not. How I can do that?
You can add a boolean property and custom deserializer which just reads given value and returns true. Jackson invokes custom deserializer only if property exists in payload.
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.io.File;
import java.io.IOException;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./src/main/resources/test.json");
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(jsonFile, User.class));
}
}
class PropertyExistsJsonDeserializer extends JsonDeserializer<Boolean> {
#Override
public Boolean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
p.readValueAsTree(); //consume value
return Boolean.TRUE;
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
class User {
private int id;
#JsonDeserialize(using = PropertyExistsJsonDeserializer.class)
#JsonProperty("picture")
private boolean pictureAvailable;
//getters, setters, toString
}
Above code prints:
User{id=123, pictureAvailable=true}

Need help fetch friends from vkontakte

I have written oauth2 social client but could not fetch authorized user's friends list
Please have a look at my code to see what's missing/
regards
please look at #RequestMapping("vkontakte/friends")
java 1.8 spring security
#SpringBootApplication
#RestController
#EnableOAuth2Client
public class SocialApplication extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oAuth2ClientContext;
#RequestMapping({ "/user", "/me" })
public Map<String, String> user(Principal principal) {
Map<String, String> map = new LinkedHashMap<>();
map.put("name", principal.getName());
return map;
}
//TODO как это оформить на фронтенде?
#RequestMapping("/vkontakte/friends")
public Map<String,String> friends() {
OAuth2RestTemplate vkTemplate = new OAuth2RestTemplate(vk(), oAuth2ClientContext);
UserInfoTokenServices tokenServicesvk = new UserInfoTokenServices(vkResource().getUserInfoUri(), vk().getClientId());
tokenServicesvk.setRestTemplate(vkTemplate);
ObjectNode resultNode = vkTemplate.getForObject(vkResource().getUserFriendsInfoUri(), ObjectNode.class);
ArrayNode data = (ArrayNode) resultNode.get("data");
Map<String, String> map = new LinkedHashMap<>();
for (JsonNode dataNode : data) {
//TODO надо как то правильно все получить?
}
return map;
In order to fetch friends from Vkontakte, you must declare a friend object, which will contain all the fields JSON structure of a friend has.
According to documentation, every friend has an id, first name, and last name, however, the response object is a little bit more complex than we need, so you might remove what you do not need.
Assuming we need all of the attributes of the response we can come up with two objects: result and friend.
Vkontakte friend object
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class VkontakteFriend {
private Long id;
#JsonProperty("first_name")
private String firstName;
#JsonProperty("last_name")
private String lastName;
}
Generic result object
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class FriendResponse {
private Long count;
private List<VkontakteFriend> items;
}
The only thing is left is to call API and get your response mapped to Java objects.
vkTemplate.getForObject(vkResource().getUserFriendsInfoUri(), FriendResponse.class);

IDE does not show getters and setters generated by Lombok for a Jackson annotated class

I use Intellij Idea 2019.1.2 community edition for my Java projects. I have a Jackson 2.9.6 annotated POJO which uses Lombok 1.18.0 to generate the getters and setters for the pojo. I have some "client" code to deserialize a sample Json text to the Pojo class. The deserialization works fine, without any compilation issues and the class file for the pojo actually has all the getters and setters. But, the IDE does not show any getters and setters for the pojo in the "client" code.
Invalidating the caches of the IDE and restarting it did not solve the problem. How do I find out the cause for this problem and fix it ?
Sample Json :
{
"id": "1234",
"number" : 1,
"items" : [
{
"item1" : "blah...more fields."
},
{
"item2" : "blah...more fields."
}
],
"someBigObject:" : {
"my_comment" : "i don't really care about validating this object.",
"fields" : "more fields here",
"objects" : "more objects here"
},
"message" : "hello"
}
Pojo for above Json, generated by http://www.jsonschema2pojo.org/ :
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"id",
"number",
"items",
"someBigObject:",
"message"
})
#Data
#NoArgsConstructor
public class Example {
#JsonProperty("id")
#NonNull public String id;
#JsonProperty("number")
public Long number;
#JsonProperty("items")
public List<Item> items = null;
#JsonProperty("someBigObject:")
public Object someBigObject;
#JsonProperty("message")
public String message;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"item1",
"item2"
})
#Data
#NoArgsConstructor
public static class Item {
#JsonProperty("item1")
public String item1;
#JsonProperty("item2")
public String item2;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
}
Sample code to try the above pojo :
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class JunkTest {
public static void main(String [] args) throws IOException {
String json = "Put the json here !!!";
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
Example pojo = mapper.readValue(json, Example.class);
pojo.getClass();//Cannot see any other getters and setters !
}
}
To resolve this problem you need :
1 - Lombok plugin is installed in Intellij IDEA.
https://projectlombok.org/setup/intellij
Add the Lombok IntelliJ plugin to add lombok support for IntelliJ:
Go to File > Settings > Plugins
Click on Browse repositories...
Search for Lombok Plugin
Click on Install plugin
Restart IntelliJ IDEA
2 - Annotation processing is turned on for your project.
Refer - https://stackoverflow.com/a/27430992

Categories