jackson: mapping some fields of json to inner fields of class - java

I want to map some fields of json to inner fields of a class. e.g
{
values:[{
"name":"Abc",
"age":18,
"street":"test",
"postalcoad":"1231412"
},
{
"name":"ccvb",
"age":20,
"street":"test2",
"postalcoad":"123"
}
]}
Following i my java class
#JsonIgnoreProperties(ignoreUnknown = true)
public class Customer{
#JsonProperty("name")
private string name;
#JsonProperty("age")
private int age;
private Address address;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Address{
#JsonProperty("street")
private string street;
#JsonProperty("postalcode")
private string postalcode;
}
ObjectMapper mapper = new ObjectMapper();
Customer[] c = mapper.readValue(mapper.readTree(json).get("values").toString(), Customer[].class);
It returns me Customer object without Address. Any idea how can i create Address object from this json.

One of the options is to use #JsonCreator annotation:
#JsonCreator
public Customer(
#JsonProperty("name") String name,
#JsonProperty("age") int age,
#JsonProperty("street") String street,
#JsonProperty("postalcode") String postalcode
) {
this.name = name;
this.age = age;
this.address = new Address();
this.address.street = street;
this.address.postalcode = postalcode;
}
Second option is create custom deserializer and bind your class with deserializer using #JsonDeserialize annotation
#JsonDeserialize(using = CustomerDeserializer.class)
public static class Customer{
....
}
public class CustomerDeserializer extends StdDeserializer<Customer> {
public CustomerDeserializer() {
super(Customer.class);
}
#Override
public Customer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Customer customer = new Customer();
JsonNode treeNode = p.readValueAsTree();
if (treeNode == null) {
return null;
}
customer.setName(treeNode.get("name").asText());
customer.setAge(treeNode.get("age").asInt());
Address address = new Address();
address.setStreet(treeNode.get("street").asText());
address.setPostalcode(treeNode.get("postalcode").asText());
customer.setAddress(address);
return customer;
}
}
As third option, you can use #JsonAnySetter with some kind of post construct processing:
public interface PostConstruct {
void postConstruct();
}
public class Customer implements PostConstruct {
//mapping
private Map<String, Object> additionalFields = new HashMap<>();
#JsonAnySetter
public void setAdditionalValue(String key, Object value) {
additionalFields.put(key, value);
}
#Override
public void postConstruct() {
address = new Address();
address.setStreet(String.valueOf(additionalFields.get("street")));
address.setPostalcode(String.valueOf(additionalFields.get("postalcode")));
}
}
public static class PostConstructDeserializer extends DelegatingDeserializer {
private final JsonDeserializer<?> deserializer;
public PostConstructDeserializer(JsonDeserializer<?> deserializer) {
super(deserializer);
this.deserializer = deserializer;
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return deserializer;
}
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
Object result = _delegatee.deserialize(jp, ctxt);
if (result instanceof PostConstruct) {
((PostConstruct) result).postConstruct();
}
return result;
}
}
//using of post construct deserializer
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDesc,
final JsonDeserializer<?> deserializer) {
return new PostConstructDeserializer(deserializer);
}
});
mapper.registerModule(module);

I would create a custom deserializer and inside of it call the default deserializer for Customer and then call the default deseriazlier for Address. Then you add the address to the customer object. This way they both look at the same json but you get two different objects out and you can connect them the way you want.
To call a standard deserializer from a custom deseriazlier see this answer: How do I call the default deserializer from a custom deserializer in Jackson.

Related

Deserialize multiple json fields into single java property

I want to convert a json into Java class by having custom deserializer.
I'm able to serialize ACC_NUM, NAME and any other fields from json but not sure what can be done to convert MOBILE_NUMBER_1,MOBILE_NUMBER_2 like fields into single JSONArray(See AccountInfo class). There can be many more fields like this and count also is not fixed. Example there can be ADDRESS_1, ADDRESS_2 till ADDRESS_20 and so on and all this fields should go in JSONArray of ADDRESS after deserilization.
I have a Map of Map which holds info like this:
{
"accountInfo": {
"ACC_NUM": "1234567890",
"NAME": "John Cena",
"MOBILE_NUMBER_1": "12376534",
"MOBILE_NUMBER_2": "12376534",
"MOBILE_NUMBER_3": "12376534",
"MOBILE_NUMBER_4": "12376534"
},
"someOther": {
//similer to above
}
}
This info I want to convert to the following class CommonInfo:
public class CommonInfo {
private AccountInfo accountInfo;
//other properties...
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class AccountInfo {
#JsonProperty("ACC_NUM")
private FieldValue<BigInteger> accountNum;
#JsonProperty("NAME")
private FieldValue<String> name;
#JsonProperty("MOBILE_NUMBER")
private FieldValue<JSONArray> mobileNumber;
}
//FieldValue class
public interface FieldValue<T> {
T getInitialValue();
void setInitialValue(T initialValue);
T getValue();
void setValue(T value);
}
#JsonInclude(JsonInclude.Include.ALWAYS)
public class FieldValueImpl<T> implements FieldValue<T> {
protected T initialValue;
protected T value;
//getters, setters, cons..
}
My service code takes json/Map and tries to convert it to CommonInfo class
#Service
public class MyService {
private final ObjectMapper jsonMapper = new ObjectMapper();
#PostConstruct
protected void init() {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(FieldValue.class, new FieldValueSerializer());
simpleModule.addDeserializer(FieldValue.class, new FieldValueDeserializer());
jsonMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
jsonMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
jsonMapper.registerModule(simpleModule);
}
public CommonInfo setPojoResult(Map<String, LinkedHashMap<String, String>> contentAsMap) {
return jsonMapper.convertValue(contentAsMap, CommonInfo.class);
}
}
Serializer and Deserializer looks like this:
public class FieldValueDeserializer extends JsonDeserializer<FieldValue<?>> implements ContextualDeserializer {
private JavaType valueType;
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
var deserializer = new FieldValueDeserializer();
if (property == null) {
deserializer.valueType = ctxt.getContextualType().containedType(0);
} else {
var wrapperType = property.getType();
var valueType = wrapperType.containedType(0);
deserializer.valueType = valueType;
}
return deserializer;
}
#Override
public FieldValue<?> deserialize(JsonParser parser, DeserializationContext context) throws IOException {
FieldValueDeserializer deserializer = new FieldValueDeserializer();
deserializer.getKnownPropertyNames();
FieldValue<?> fieldValueImpl = new FieldValueImpl<>();
if (valueType.toString().contains("java.time.LocalDate")) {
JsonNode node = parser.getCodec().readTree(parser);
FieldValue<LocalDate> f1 = new FieldValueImpl<>();
f1.setValue(DateUtils.convertJulianToLocalDate(node.textValue()));
return f1;
} else {
fieldValueImpl.setValue(context.readValue(parser, valueType));
}
return fieldValueImpl;
}
}
//--
public class FieldValueSerializer extends StdSerializer<FieldValue> {
public FieldValueSerializer() {
this(null);
}
public FieldValueSerializer(Class<FieldValue> vc) {
super(vc);
}
#Override
public void serialize(FieldValue value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(String.valueOf(value.getCurValue()));
}
}

How to remove a field from POJO

public class user {
private String planId;
private String eid;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Collection<String> userIds;
}
I have a pojo like above
and the code which is use for creating the json request object is this :
public UserCollection getUserCollection(final user args) {
Map<String, String> headersMap = new HashMap<>();
ObjectMapper jacksonMapper = new ObjectMapper();
jacksonMapper.disable(MapperFeature.USE_ANNOTATIONS); // this line is creating the userIds field to reflect
//By any code we can remove the userId field from the args object
String responseBody = null;
String responseStatus = null;
String jsonRequestBody = jacksonMapper.writeValueAsString(args);
}
I just want to remove userIds from the args by not removing any of the above code.
Thanks in advance.
I don't know how you should solve this without removing the annotation processing code, you should maybe add a custom serializer. You can read further about the topic at here.
public class HelloWorld {
public static void main(String[] args) throws JsonProcessingException {
User myItem = new User("planId", "eId", List.of("1", "2"));
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(User.class, new UserSerializer());
mapper.registerModule(module);
String serialized = mapper.writeValueAsString(myItem);
}
#Data
#AllArgsConstructor
public static class User {
private String planId;
private String eId;
private Collection<String> userIds;
}
public static class UserSerializer extends StdSerializer<User> {
public UserSerializer() {
this(null);
}
public UserSerializer(Class<User> t) {
super(t);
}
#Override
public void serialize(User value, JsonGenerator gen, SerializerProvider provider) throws IOException{
gen.writeStartObject();
gen.writeStringField("planId", value.planId);
gen.writeStringField("eId", value.eId);
gen.writeEndObject();
}
}
}
If you had to use it without annotations, and cannot add a custom serializer you should map the User class into a more specific class without the field in question and then serialize that one.

Deserialize JSON array to a single Java object with Jackson

The idea is that I'd like to convert a JSON array ["foo", "bar"] into a Java object so I need to map each array element to property by index.
Suppose I have the following JSON:
{
"persons": [
[
"John",
"Doe"
],
[
"Jane",
"Doe"
]
]
}
As you can see each person is just an array where the first name is an element with index 0 and the last name is an element with index 1.
I would like to deserialize it to List<Person>.
I use mapper as follows:
mapper.getTypeFactory().constructCollectionType(List.class, Person.class)
where Person.class is:
public class Person {
public final String firstName;
public final String lastName;
#JsonCreator
public Person(#JsonProperty() String firstName, #JsonProperty String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
I was wondering if I can somehow specify array index as #JsonProperty argument instead of it's key name?
Thanks to bureaquete for suggestion to use custom Deserializer. But it was more suitable for me to register it with SimpleModule instead of #JsonDeserialize annotation. Below is complete JUnit test example:
#RunWith(JUnit4.class)
public class MapArrayToObjectTest {
private static ObjectMapper mapper;
#BeforeClass
public static void setUp() {
mapper = new ObjectMapper();
SimpleModule customModule = new SimpleModule("ExampleModule", new Version(0, 1, 0, null));
customModule.addDeserializer(Person.class, new PersonDeserializer());
mapper.registerModule(customModule);
}
#Test
public void wrapperDeserializationTest() throws IOException {
//language=JSON
final String inputJson = "{\"persons\": [[\"John\", \"Doe\"], [\"Jane\", \"Doe\"]]}";
PersonsListWrapper deserializedList = mapper.readValue(inputJson, PersonsListWrapper.class);
assertThat(deserializedList.persons.get(0).lastName, is(equalTo("Doe")));
assertThat(deserializedList.persons.get(1).firstName, is(equalTo("Jane")));
}
#Test
public void listDeserializationTest() throws IOException {
//language=JSON
final String inputJson = "[[\"John\", \"Doe\"], [\"Jane\", \"Doe\"]]";
List<Person> deserializedList = mapper.readValue(inputJson, mapper.getTypeFactory().constructCollectionType(List.class, Person.class));
assertThat(deserializedList.get(0).lastName, is(equalTo("Doe")));
assertThat(deserializedList.get(1).firstName, is(equalTo("Jane")));
}
}
class PersonsListWrapper {
public List<Person> persons;
}
class Person {
final String firstName;
final String lastName;
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
class PersonDeserializer extends JsonDeserializer<Person> {
#Override
public Person deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
JsonNode node = jp.readValueAsTree();
return new Person(node.get(0).getTextValue(), node.get(1).getTextValue());
}
}
Note that if you do not need wrapper object, you can deserialize JSON array
[["John", "Doe"], ["Jane", "Doe"]] directly to List<Person> using mapper as follows:
List<Person> deserializedList = mapper.readValue(inputJson, mapper.getTypeFactory().constructCollectionType(List.class, Person.class));
It is easy to serialize, but not so easy to deserialize in such manner;
The following class can be serialized into an array of strings as in your question with #JsonValue;
public class Person {
private String firstName;
private String lastName;
//getter,setter,constructors
#JsonValue
public List<String> craeteArr() {
return Arrays.asList(this.firstName, this.lastName);
}
}
But to deserialize, I had to create a wrapper class, and use custom deserialization with #JsonDeserialize;
public class PersonWrapper {
#JsonDeserialize(using = CustomDeserializer.class)
private List<Person> persons;
//getter,setter,constructors
}
and the custom deserializer itself;
public class CustomDeserializer extends JsonDeserializer<List<Person>> {
#Override
public List<Person> deserialize(JsonParser jsonParser, DeserializationContext context) throws IOException {
JsonNode node = jsonParser.readValueAsTree();
ObjectMapper mapper = new ObjectMapper();
return IntStream.range(0, node.size()).boxed()
.map(i -> {
try {
List<String> values = mapper.readValue(node.get(i).toString(), List.class);
return new Person().setFirstName(values.get(0)).setLastName(values.get(1));
} catch (IOException e) {
throw new RuntimeException();
}
}).collect(Collectors.toList());
}
}
You need to put proper validation in deserializer logic to check that each mini-array contains exactly two values, but this works well.
I'd rather use these steps, and maybe to hide #JsonDeserialize, I'd do the following;
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotationsInside
#JsonDeserialize(using = CustomDeserializer.class)
public #interface AcceptPersonAsArray {}
So you can use some custom annotation in PersonWrapper
public class PersonWrapper {
#AcceptPersonAsArray
private List<Person> persons;
//getter,setter,constructors
}

Jackson: Keep references to keys in map values when deserializing

I have the following JSON with a map from user IDs to user details:
{
"users": {
"john": { "firstName": "John", "lastName": "Doe" },
"mark": { "firstName": "Mark", "lastName": "Smith" }
}
}
and I'm using the following code to deserialize the JSON into a Java objects:
class User {
public String userID;
public String firstName;
public String lastName;
}
public class Users {
public Map<String, User> users;
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Reader source = Files.newBufferedReader(Paths.get("test.json"));
Users all = mapper.readValue(source, Users.class);
// ...
}
}
After the deserialization, I want the field User.userID to be set to the corresponding key in the users map.
For example all.users.get("john").userID should be "john".
How can I do that?
Create a custom deserializer for User object and use this for the Map. Here's a full example:
#Test
public void test() throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
Data data = mapper.readValue("{\"users\": {\"John\": {\"id\": 20}, \"Pete\": {\"id\": 30}}}", Data.class);
assertEquals(20, data.users.get("John").id);
assertEquals(30, data.users.get("Pete").id);
assertEquals("John", data.users.get("John").name);
assertEquals("Pete", data.users.get("Pete").name);
}
public static class Data {
#JsonDeserialize(contentUsing = Deser.class)
public Map<String, User> users;
}
public static class User {
public String name;
public int id;
}
public static class Deser extends JsonDeserializer<User> {
#Override
public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String name = ctxt.getParser().getCurrentName();
User user = p.readValueAs(User.class);
user.name = name; // This copies the key name to the user object
return user;
}
}
The simplest solution for the problem is to implement a custom deserializer for the class in which you need the map key (see john16384's answer). This is however cumbersome if you have multiple maps with different value types in your JSON because you'd need one deserializer per type.
In this case, there is a better solution: I would create a custom #JsonMapKey annotation to mark the target properties for the map keys, and then register a generic custom deserializer that processes all occurrences of the annotation. These are the parts you need for this:
Custom #JsonMapKey annotation:
/**
* Annotation used to indicate that the annotated property shall be deserialized to the map key of
* the current object. Requires that the object is a deserialized map value.
*
* Note: This annotation is not a standard Jackson annotation. It will only work if this is
* explicitly enabled in the {#link ObjectMapper}.
*/
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface JsonMapKey {
}
Custom deserializer that processes the #JsonMapKey annotations:
public class JsonMapKeyDeserializer extends DelegatingDeserializer {
private static final long serialVersionUID = 1L;
private BeanDescription beanDescription;
public JsonMapKeyDeserializer(JsonDeserializer<?> delegate, BeanDescription beanDescription) {
super(delegate);
this.beanDescription = beanDescription;
}
#Override
protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
return new JsonMapKeyDeserializer(newDelegatee, beanDescription);
}
#Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String mapKey = p.getCurrentName();
Object deserializedObject = super.deserialize(p, ctxt);
// set map key on all fields annotated with #JsonMapKey
for (BeanPropertyDefinition beanProperty : beanDescription.findProperties()) {
AnnotatedField field = beanProperty.getField();
if (field != null && field.getAnnotation(JsonMapKey.class) != null) {
field.setValue(deserializedObject, mapKey);
}
}
return deserializedObject;
}
}
Registration of the custom deserializer in the ObjectMapper:
private static void registerJsonMapKeyAnnotation(ObjectMapper objectMapper) {
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,
BeanDescription beanDescription, JsonDeserializer<?> originalDeserializer) {
return new JsonMapKeyDeserializer(originalDeserializer, beanDescription);
}
});
objectMapper.registerModule(module);
}
Then you only need to annotate the field to be used for the map key...
class User {
#JsonMapKey
public String userID;
public String firstName;
public String lastName;
}
... and deserialize your JSON with the prepared ObjectMapper:
Users all = registerJsonMapKeyAnnotation(new ObjectMapper()).readValue(source, Users.class);
First Create the ObjectMapper class object than configure it.
Try following one.
Sample Code
Map<K, V> map;
ObjectMapper mapper = new ObjectMapper();
mapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
map = mapper.readValue(jsonStr, new TypeReference<Map<K, V>>() {});
than you can get the value using Map.

Custom JSON Deserialization with Jackson

I'm using the Flickr API. When calling the flickr.test.login method, the default JSON result is:
{
"user": {
"id": "21207597#N07",
"username": {
"_content": "jamalfanaian"
}
},
"stat": "ok"
}
I'd like to parse this response into a Java object:
public class FlickrAccount {
private String id;
private String username;
// ... getter & setter ...
}
The JSON properties should be mapped like this:
"user" -> "id" ==> FlickrAccount.id
"user" -> "username" -> "_content" ==> FlickrAccount.username
Unfortunately, I'm not able to find a nice, elegant way to do this using Annotations. My approach so far is, to read the JSON String into a Map<String, Object> and get the values from there.
Map<String, Object> value = new ObjectMapper().readValue(response.getStream(),
new TypeReference<HashMap<String, Object>>() {
});
#SuppressWarnings( "unchecked" )
Map<String, Object> user = (Map<String, Object>) value.get("user");
String id = (String) user.get("id");
#SuppressWarnings( "unchecked" )
String username = (String) ((Map<String, Object>) user.get("username")).get("_content");
FlickrAccount account = new FlickrAccount();
account.setId(id);
account.setUsername(username);
But I think, this is the most non-elegant way, ever. Is there any simple way, either using Annotations or a custom Deserializer?
This would be very obvious for me, but of course it doesn't work:
public class FlickrAccount {
#JsonProperty( "user.id" ) private String id;
#JsonProperty( "user.username._content" ) private String username;
// ... getter and setter ...
}
You can write custom deserializer for this class. It could look like this:
class FlickrAccountJsonDeserializer extends JsonDeserializer<FlickrAccount> {
#Override
public FlickrAccount deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Root root = jp.readValueAs(Root.class);
FlickrAccount account = new FlickrAccount();
if (root != null && root.user != null) {
account.setId(root.user.id);
if (root.user.username != null) {
account.setUsername(root.user.username.content);
}
}
return account;
}
private static class Root {
public User user;
public String stat;
}
private static class User {
public String id;
public UserName username;
}
private static class UserName {
#JsonProperty("_content")
public String content;
}
}
After that, you have to define a deserializer for your class. You can do this as follows:
#JsonDeserialize(using = FlickrAccountJsonDeserializer.class)
class FlickrAccount {
...
}
Since I don't want to implement a custom class (Username) just to map the username, I went with a little bit more elegant, but still quite ugly approach:
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(in);
JsonNode user = node.get("user");
FlickrAccount account = new FlickrAccount();
account.setId(user.get("id").asText());
account.setUsername(user.get("username").get("_content").asText());
It's still not as elegant as I hoped, but at least I got rid of all the ugly casting.
Another advantage of this solution is, that my domain class (FlickrAccount) is not polluted with any Jackson annotations.
Based on #MichaƂ Ziober's answer, I decided to use the - in my opinion - most straight forward solution. Using a #JsonDeserialize annotation with a custom deserializer:
#JsonDeserialize( using = FlickrAccountDeserializer.class )
public class FlickrAccount {
...
}
But the deserializer does not use any internal classes, just the JsonNode as above:
class FlickrAccountDeserializer extends JsonDeserializer<FlickrAccount> {
#Override
public FlickrAccount deserialize(JsonParser jp, DeserializationContext ctxt) throws
IOException, JsonProcessingException {
FlickrAccount account = new FlickrAccount();
JsonNode node = jp.readValueAsTree();
JsonNode user = node.get("user");
account.setId(user.get("id").asText());
account.setUsername(user.get("username").get("_content").asText());
return account;
}
}
You can also use SimpleModule.
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override public JsonDeserializer<?> modifyDeserializer(
DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (beanDesc.getBeanClass() == YourClass.class) {
return new YourClassDeserializer(deserializer);
}
return deserializer;
}});
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
objectMapper.readValue(json, classType);
I made it this way:
public class FlickrAccount {
private String id;
#JsonDeserialize(converter = ContentConverter.class)
private String username;
private static class ContentConverter extends StdConverter<Map<String, String>, String> {
#Override
public String convert(Map<String, String> content) {
return content.get("_content"));
}
}
}
You have to make Username a class within FlickrAccount and give it a _content field

Categories