I am trying to map a JSON structure to a specific POJO that doesn't really match the JSOM, here is a simple example:
JSON
{
"strA": "MyStr",
"Street": "1st Lane",
"Number": "123"
}
POJO
#JsonIgnoreProperties(ignoreUnknown = true)
public class ClassA {
#JsonProperty("strA")
private String strA;
private Address address;
//Constructor, getter,setter
#JsonRootName("Address")
#JsonIgnoreProperties(ignoreUnknown = true)
public class Address {
private String address;
public Address() {
}
public String getAddress() {
return address;
}
#JsonAnySetter
public void setAddress(#JsonProperty("Street") String street, #JsonProperty("Number")String number) {
this.address = number + " " + street;
}
}
Now, Address is properly created from the sample JSON (only made it work with the JsonAnySetter unfortunately), but I can't get ClassA to be created properly.
I've tried annotating the Address property of it, but to no avail.
How can I achieve this in a "simple" way? This is important as this example is simple, but my real use cases involves several composed classes that need information from the JSON root + from complex JSON elements with different names.
Thank you for your time.
The JPA #Convert annotation says it's applicable to a method (as well as field and type).
What is an example of a situation where it is useful?
#Convert allows us to map JDBC types to Java classes.
Let's consider the code block below:-
public class UserName implements Serializable {
private String name;
private String surname;
// getters and setters
}
#Entity(name = "UserTable")
public class User {
private UserName userName;
//...
}
Now we need to create a converter that transforms the PersonName attribute to a database column and vice-versa.
Now we can annotate our converter class with #Converter and implement the AttributeConverter interface. Parametrize the interface with the types of the class and the database column, in that order:
#Converter
public class UserNameConverter implements
AttributeConverter<UserName, String> {
private static final String SEPARATOR = ", ";
#Override
public String convertToDatabaseColumn(UserName userName) {
if (userName == null) {
return null;
}
....
}
}
In order to use the converter, we need to add the #Convert annotation to the attribute and specify the converter class we want to use:
#Entity(name = "PersonTable")
public class User {
#Convert(converter = UserNameConverter.class)
private UserName userName;
// ...
}
For more details you can refer below:- jpa-convert
I am trying to obtain a value from a parent node using Jackson.
I know this is possible to achieve with custom deserialisers, but then there is too much boilerplate because you suddenly have to handle everything manually.
It sounds like something quite simple but didn't find a way to do it.
To illustrate what I want - If we have a simple User with Address...
#JsonDeserialize(builder = User.Builder.class)
public class User
{
private long id;
private String firstName;
private Address address;
...
public static class Builder
{
public Builder withId(long id);
public Builder withFirstName(String value);
public Builder withAddress(Address address);
public User create();
}
}
If we have the same for address
#JsonDeserialize(builder = Address.Builder.class)
public class Address
{
...
public static class Builder
{
public Builder withUserId(long id); // is there a way to ask for the parent id here?
public Builder withStreetName(String value);
public Address create();
}
}
Sample input:
{
"id": 7,
"firstName" : "John",
"lastName" : "Smith",
"address" : {
"streetName": "1 str"
}
}
No, I don't think you can with any of the existing Jackson code. The only thing I believe that can cross parent/child relationships like that is type serialization/deserialization and the UNWRAP_ROOT_VALUE support.
If you want something like that, you'd either need to use a custom deserializer for User, or customize the User constructor to build a new address with the correct UserId before adding it to the builder's internal state. Here's an example (using Lombok to handle the boilerplate generation of builders):
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Value;
import lombok.experimental.Wither;
public class Scratch {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
String json = "{\"id\":1234,\"address\":{\"street\":\"123 Main St.\"}}";
User user = mapper.readValue(json, User.class);
System.out.println(user.toString());
}
#Value
#JsonDeserialize(builder = User.UserBuilder.class)
public static class User {
private final int id;
private final Address address;
#Builder(toBuilder = true)
public User(int id, Address address) {
this.id = id;
// Build a new address with the user's ID
this.address = address.withUserId(id);
}
#JsonPOJOBuilder(withPrefix = "")
public static class UserBuilder {}
}
#Value
#Builder(toBuilder = true)
#JsonDeserialize(builder = Address.AddressBuilder.class)
public static class Address {
#Wither
private final int userId;
private final String street;
#JsonPOJOBuilder(withPrefix = "")
public static class AddressBuilder {}
}
}
This consumes the following json:
{
"id": 1234,
"address": {
"street": "123 Main St."
}
}
and produces the following output:
Scratch.User(id=1234, address=Scratch.Address(userId=1234, street=123 Main St.))
class Identifier {
private long id;
private String type;
private List<Status> statuses;
}
class Customer {
private Identifier identifier;
}
class CustomerProfile {
private Customer customer;
}
class CustomerIdentifierDO {
private long id;
}
class CustomeDO {
private CustomerIdentiferDO custID;
}
class CustomerProfileDO {
private String category;
private List<Status> custStatuses;
private CustomeDO customer;
}
#Mapper
public interface CustomerProfileMapper {
CustomerProfile toCustomerProfile(CustomerProfileDO profileDO) ;
Customer toCustomer(CustomerDO customerDO);
Identifier toIdentifier(CustomerIdentifierDO identifierDO);
}
Everything works fine till this. Now I want to map custStatuses, category of CustomerProfileDO class to statuses and type of Identifier class. I've no idea how to supply CustomerProfileDO object to toIdentifier mapping method, so that I can include the mapping there itself. I tried following
#Mappings({
#Mapping(target = "customer.identifier.type", source = "category")
})
CustomerProfile toCustomerProfile(CustomerProfileDO profileDO) ;
But this nested mapping is overriding all the mapping config of below method. That should not happen.
toIdentifer(CustomerIdentifierDO identifierDO)
Is there any way to achieve this?
Currently MapStruct can pass source parameters to single methods. In order to achieve what you are looking for (without using nested target types you would need to use something like #AfterMapping. It can look like:
#Mapper
public interface CustomerProfileMapper {
CustomerProfile toCustomerProfile(CustomerProfileDO profileDO) ;
Customer toCustomer(CustomerDO customerDO);
Identifier toIdentifier(CustomerIdentifierDO identifierDO);
#AfterMapping
default void afterMapping(#MappingTarget CustomerProfile profile, CustomerProfieDO profileDO) {
Identifier identifier = profile.getCustomer().getIdentifier();
identifier.setStatus(profileDO.setStatus());
identifier.setType(profileDO.setCategory());
}
}
I am currently trying to develop a REST client that communicates with a certain online service. This online service returns some JSON responses which I wish to map to Java objects using Jackson.
An example of a JSON response would be:
{
"id" : 1,
"fields" : [ {
"type" : "anniversary",
"value" : {
"day" : 1,
"month" : 1,
"year" : 1970
}
}, {
"type" : "birthday",
"value" : {
"day" : 1,
"month" : 1,
"year" : 1970
}
}, {
"type" : "simple",
"value" : "simple string"
},{
"type": "name",
"value": {
"firstName": "Joe",
"lastName": "Brown"
}
} ]
}
NOTE the following:
this structure contains a simple id, and a collection of Field instances, each having a type and a value
the value structure is determined by the external property type
in the example given, there are 3 types -> date, name, and single value string
the birthday and anniversary types both match the date structure
there are several types which map to a single value string, like email type, twitterId type, company type etc.
My problem is that I do not seem to be able to correctly map this structure to the Java objects
Here's what I've done so far. The following are the classes and their Jackson annotations(the getters and setters are omitted).
public class Contact {
private int id;
private List<Field> fields;
}
public class Field {
private FieldType type;
private FieldValue value;
}
public enum FieldType {
EMAIL, NICKNAME, NAME, ADDRESS, BIRTHDAY, ANNIVERSARY
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type",
defaultImpl = SingleFieldValue.class)
#JsonSubTypes({
#JsonSubTypes.Type(value = NameFieldValue.class, name = "name"),
#JsonSubTypes.Type(value = DateFieldValue.class, name = "anniversary"),
#JsonSubTypes.Type(value = DateFieldValue.class, name = "birthday"),
#JsonSubTypes.Type(value = SingleFieldValue.class, name = "nickname"),
#JsonSubTypes.Type(value = SingleFieldValue.class, name = "email"),
//other types that map to SingleFieldValue
})
public abstract FieldValue {
}
public class NameFieldValue extends FieldValue {
private String firstName;
private String lastName;
}
public class DateFieldValue extends FieldValue {
private int day;
private int month;
private int year;
}
public class SingleFieldValue extends FieldValue {
private String value;
}
The ObjectMapper does not contain any configuration, the default configuration is used.
What suggestions do you have to correctly map these? I would like to avoid making custom deserializers and just traverse Json objects, like JsonNode.
NOTE: I apologize in advance for any lack of information to make this issue clear enough. Please state any problems with my formulation.
You have used an abstract class on the FieldValue level to use it in FIeld class. In that case you can construct the object with type=email and value=address which can lead to some issues...
I would recommend to create a specific classes for every type with specific FieldValue type.
The following code is serializing/deserializing JSON from/to required format from/to POJO:
public class Main {
String json = "{\"id\":1,\"fields\":[{\"type\":\"SIMPLE\",\"value\":\"Simple Value\"},{\"type\":\"NAME\",\"value\":{\"firstName\":\"first name\",\"lastName\":\"last name\"}}]}";
public static void main(String []args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(generate());
System.out.println(json);
System.out.println(objectMapper.readValue(json, Contact.class));
}
private static Contact generate() {
SimpleField simpleField = SimpleField.builder().type(FieldType.SIMPLE).value("Simple Value").build();
NameFieldValue nameFieldValue = NameFieldValue.builder().firstName("first name").lastName("last name").build();
NameField nameField = NameField.builder().type(FieldType.NAME).value(nameFieldValue).build();
return Contact.builder().id(1).fields(Arrays.asList(simpleField, nameField)).build();
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = SimpleField.class, name = "SIMPLE"),
#JsonSubTypes.Type(value = NameField.class, name = "NAME")
})
interface Field {
FieldType getType();
Object getValue();
}
enum FieldType {
SIMPLE, NAME
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
class Contact {
private int id;
private List<Field> fields;
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
class SimpleField implements Field {
private FieldType type;
private String value;
#Override
public FieldType getType() {
return this.type;
}
#Override
public String getValue() {
return this.value;
}
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
class NameField implements Field {
private FieldType type;
private NameFieldValue value;
#Override
public FieldType getType() {
return this.type;
}
#Override
public Object getValue() {
return this.value;
}
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
class NameFieldValue {
private String firstName;
private String lastName;
}
I have used lombok library here just to minimize the code and avoiding creating getters/setters as well as constructors. You can delete lombok annotations and add getters/setters/constructors and code will work the same.
So, the idea is that you have a Contact class (which is root of your JSON) with a list of Fields (where Field is an interface). Every Field type has own implementation like NameField implements Field and has NameFieldValue as a property. The trick here is that you can change getValue() method declaration and declare that it returns the common interface or Object (I used Object but interface will work as well).
This solution doesn't require any custom serializers/deserializers and easy in maintenance.