I am reading a JSON String where every field is optional and i need to either get the values from config or set it to a default value.
Say my JSON is -
{
"action": {
"onWarning":{
"alert":{
"isEnabled": true,
"name": "DVS.sd_service.data-validation.bigdata.warning"
}
},
"onError":{
"alert":{
"isEnabled": false,
"name": "DVS.sd_service.data-validation.bigdata.error"
}
}
}
}
And my code to read this JSON is -
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerModule(new Jdk8Module());
JobConfig jobConfig = mapper.readValue(contentJson, JobConfig.class);
And below are my wrapper classes -
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class JobConfig {
public Optional<AlertConfig> action;
}
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class AlertWrapper {
public Optional<Alert> alert;
}
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class AlertConfig {
public Optional<AlertWrapper> onSuccess;
public Optional<AlertWrapper> onWarning;
public Optional<AlertWrapper> onError;
}
#AllArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class Alert{
public Optional<Boolean> isEnabled;
public Optional<String> name;
}
Now the objective here is to read OnError.alert.isEnabled however if this field or the entire onError part is not available then we have to set it to default True
The function i have written for this is :-
private Optional<Boolean> getErrorIsEnabled(JobConfig jobConfig ) {
Optional<Boolean> isEnabled = Optional.empty();
if(jobConfig.action.isPresent()) {
if(jobConfig.action.get().onError.isPresent()){
if(jobConfig.action.get().onError.get().alert.isPresent()) {
if(jobConfig.action.get().onError.get().alert.get().isEnabled.isPresent()){
isEnabled= jobConfig.action.get().onError.get().alert.get().isEnabled;
}
}
}
}
return isEnabled;
}
This way i get to find if the vale is available in config by calling the above function -
getErrorIsEnabled(jobConfig).orElse(true)
The problem is (calling the above function) this works but looks way too ugly to keep on checking if the fields are available or not at every level by calling the isPresent() funtion.
The User can entirely skip the OnError Part, or action Part, or just the alert part, or the alert.isEnabled part. There has to be a better way to acieve this! Open to suggestions or improvements or try a different approach alltogether.
What you should do is create default values for the fields like
private Optional<Boolean> isEnabled = Optional.empty();
private Optional<String> name = Optional.empty();
public Alert(Optional<Boolean> isEnabled , Optional<String> name){
this.isEnabled=isEnabled;
this.name=name;
}
And for every Object like this -
private final Alert alertDetail = new Alert(Optional.empty(), Optional.empty());
private final AlertWrapper alertWrapper = new AlertWrapper(alertDetail);
private AlertWrapper onSuccess = alertWrapper;
private AlertWrapper onWarning = alertWrapper;
private AlertWrapper onError = alertWrapper;
You will have to create constructor so you will have to change your lombok to #NoArgsConstructor also use #Getter #Setter so you can call your objects like below -
jobConfig.getAction().getOnWarning().getAlert().getIsEnabled().orElse(true)
Related
I have the following classes:
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
private String id;
private List<Reference> references;
.....
}
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Reference {
#JacksonXmlProperty(isAttribute = true)
private String ref;
public Reference(final String ref) {
this.ref = ref;
}
public Reference() { }
public String getRef() {
return ref;
}
}
When serializing to XML the format is as expected, but when I try to serialize to JSON I get the following
"users" : [
{
"references" : [
{
"ref": "referenceID"
}
]
}
]
And I need it to be:
"users" : [
{
"references" : [
"referenceID"
]
}
]
the braces enclosing the reference list I need it to be ignored without the attribute name
You can annotate the ref field in your Reference class with the JsonValue annotation that indicates that the value of annotated accessor is to be used as the single value to serialize for the instance:
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Reference {
#JacksonXmlProperty(isAttribute = true)
#JsonValue //<-- the new annotation
private String ref;
public Reference(final String ref) {
this.ref = ref;
}
public Reference() { }
public String getRef() {
return ref;
}
}
User user = new User();
user.setReferences(List.of(new Reference("referenceID")));
//it prints {"references":["referenceID"]}
System.out.println(jsonMapper.writeValueAsString(user));
Edit: it seems that the JsonValue annotation invalidates the serialization of the class as expected by the OP; to solve this problem one way is the use of a mixin class for the Reference class and putting inside the JsonValue annotation, the original Reference class will be untouched:
#Data
public class MixInReference {
#JsonValue
private String ref;
}
ObjectMapper jsonMapper = new ObjectMapper();
//Reference class is still the original class
jsonMapper.addMixIn(Reference.class, MixInReference.class);
////it prints {"references":["referenceID"]}
System.out.println(jsonMapper.writeValueAsString(user));
Sorry for the confusing title. I am using JsonSubTypes for a recent project of mine.
Please consider snippets below:
#NoArgsConstructor
#Data
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "memberType")
#JsonSubTypes({
#JsonSubTypes.Type(name = UserType.AGENT_TEXT, value = Agent.class),
#JsonSubTypes.Type(name = UserType.CUSTOMER_TEXT, value = Customer.class),
#JsonSubTypes.Type(name = UserType.GUEST_TEXT, value = Guest.class)})
public abstract class User {
private UserType memberType;
public User(UserType memberType) {
this.memberType = memberType;
}
public abstract <T> T accept(UserVisitor<T> visitor);
public abstract SomeObject buildAndGetSomeObject();
}
and the derived concrete class with error mentioned in code comment:
#Data
#NoArgsConstructor
public class Customer extends User {
private CustomerData userData;
public Customer(CustomerData userData) {
super( UserType.CUSTOMER);
this.userData = userData;
}
#Override
public <T> T accept(UserVisitor<T> visitor) {
return visitor.visit(this);
}
#Override
public SomeObject buildAndGetSomeObject() {
return SomeObject.builder()
.userType(getMemberType()) // getMemberType() here is null when ideally it should be CUSTOMER
.build();
}
}
Now, this code works fine for below payload:
{
"User": {
"memberType": "CUSTOMER",
"memberType": "CUSTOMER",
"userData": {
"hashedId": "21039cefba8a499e85d62656cbandvcs234",
"name": "******4321",
"memberType": "CUSTOMER"
}
},
}
notice how memberType has been passed twice but same fails, for single occurrence as below:
{
"User": {
"memberType": "CUSTOMER",
"userData": {
"hashedId": "21039cefba8a499e85d62656cbandvcs234",
"name": "******4321",
"memberType": "CUSTOMER"
}
},
}
I am trying to understand what is possibly wrong in the approach / design here?
It works fine if i override getMemberType() inside Customer class and do a hardcoded return but then it would fail the purpose of inheritance.
My assumption is it has something to do with property = "memberType" and memberType variable name being same.
any help is appreciated.
This is in a dropwizard application with maven
solution was to use #JsonCreator on constructor. Seems JsonSubTypes by default called NoArgsConstructor to build object.
On setting the JsonCreator, constructor got called correctly
#JsonCreator // voila
#Builder
public Customer(CustomerData userData) {
super( UserType.CUSTOMER);
this.userData = userData;
}
My data is currently stored in this format:
{
"label":["X","Y"],
"data":{
"site1":{
"week":[
{
"idWeek":"9",
"max":2,
"min":0
}
]
},
"site2":{
"week":[
{
"idWeek":"9",
"max":2,
"min":0
}
]
}
}
}
And I need to convert it into this format:
{
"label":["X","Y"],
"myClient":{
"week":[
{
"id":"9",
"access":2,
"lost":0
}
]
}
}
As you can see, I also need to take the keys (site name) and I need remove "data" and change name of property.
Any ideas on how I can do this in Java (I'm using Java 8 with Spring Boot? I'm not very good at restructuring that type of data.
UPDATE
My solutions: I did different interfaces and I used RestTemplate! I don't know it's the best solution however it worked.
public Optional<Consolidate> getCasosPorEstado() {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<Consolidate> res = restTemplate.exchange(PATH_CONSOLIDATE, HttpMethod.GET, null,
new ParameterizedTypeReference<Consolidate>() {
});
return Optional.of(res.getBody());
}
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({ "label", "data" })
#Data
public class Consolidate {
#JsonProperty("label")
private List<String> labels = null;
#JsonProperty("data")
private MyClient client;
}
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({ "site1" })
class MyClient {
#JsonProperty("Site")
Site SiteObject;
}
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({ "week" })
class MS {
#JsonProperty("week")
private List<Week> week = null;
}
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Semana {
#JsonAlias("idWeek")
private String id;
#JsonAlias("max")
private Double access;
#JsonAlias("min")
private Double lost;
}
I'm trying to map source object which property is set to null to destination object of which this property is set to another object.
Expected result would be that property of destination object will be null after mapping. Instead of that, this property is set to an object and all of its properties are set to null.
Here is an example:
public class ModelMapperTest {
public static void main(String[] args) {
ModelMapper modelMapper = new ModelMapper();
User user = new User();
user.setStatus(null);
StatusDto statusDto = new StatusDto();
statusDto.setId(1);
statusDto.setName("Active");
UserDto userDto = new UserDto();
userDto.setStatus(statusDto);
// user.status=null, userDto.status=StatusDto(id=1, name="Active")
modelMapper.map(user, userDto);
System.out.println("user = " + user);
System.out.println("userDto = " + userDto);
}
#Getter
#Setter
#ToString
public static class User {
private Status status;
}
#Getter
#Setter
#ToString
public static class Status {
private Integer id;
private String name;
}
#Getter
#Setter
#ToString
public static class UserDto {
private StatusDto status;
}
#Getter
#Setter
#ToString
public static class StatusDto {
private Integer id;
private String name;
}
}
Output:
user = ModelMapperTest.User(status=null)
userDto = ModelMapperTest.UserDto(status=ModelMapperTest.StatusDto(id=null, name=null))
Is it possible to somehow configure model mapper to sets UserDto.status to null?
I know this is an older question and you seem to have moved on to a different library, but I had the same problem recently and came up with this solution (building on your example):
Converter<?, ?> preserveNullConverter = (context) ->
context.getSource() == null
? null
: modelMapper.map(context.getSource(), context.getDestinationType());
modelMapper.createTypeMap(User.class, UserDto.class)
.addMappings(mapper -> mapper.using(preserveNullConverter).map(User::getStatus, UserDto::setStatus));
It's not ideal because the .addMappings part needs to be done for every property where the issue occurs, but at least the Converter can be reused.
Trying to save One to Many JPA relationship. I have written a custom controller. I am getting only the first id in giftSet and not all the ids. I have simplified the code.
My Post request-
{
"name": "Project 7",
"giftSet": [
{
"id": "1"
},
{
"id":"33"
}
]
}
class Holiday{
private String name;
private Set<GiftConfig> giftSets;
}
class GiftSet {
private Integer id;
private Holiday holiday;
}
class GiftConfig {
private Integer id;
private String name;
}
#RequestMapping(method = RequestMethod.POST, value="/api/saveholiday")
public ResponseEntity<Map<String, Holiday>> saveHoliday(#RequestBody Holiday holiday) {
System.out.println(holiday);
return null;
}
First, I add multiple GiftConfig. After that, while creating Holiday, I add details for GiftSet as well.
In debug mode, I see only id 1 in giftSet and not both ids 1 and 33.
Note- Changing Set to List is not an option.
Introduction
I see 2 problems and one possible last issue.
You are missing setters/getters in order for de-serialization to work on the JSON.
Your payload doesn't seem to be working for me.
As pcoates mentioned in a comment, you could also use #JsonAutoDetect(fieldVisibility = Visibility.ANY) - but I haven't tested this.
Finally, also be careful about having a circular reference if you convert from java back to JSON. I see that a Holiday has a set of giftSets, but a giftSet points to a holiday.
If the gitset points to the same parent holiday, this is a circular reference and will crash.
Getters and Setters
Your problem is that you are missing getters and setters.
Either use lombok and add a #data annotation or add a getter and setter .
#Data
public static class Holiday{
private String name;
private Set<GiftSet> giftSets;
}
#Data
public static class GiftSet {
private Integer id;
private Holiday holiday;
}
Payload
I used the following payload:
{
"name": "HolidaySet",
"giftSets": [
{
"id": 1111,
"holiday": {
"name": null,
"giftSets": null
}
},
{
"id": 1112,
"holiday": {
"name": null,
"giftSets": null
}
}
]
}
Quick Test
I did a quick test to see what the payload should be like.
#RequestMapping(method = RequestMethod.POST, value="/api/saveholiday")
public ResponseEntity<Map<String, Holiday>> saveHoliday(#RequestBody Holiday holiday) throws JsonProcessingException {
System.out.println(holiday);
fakeItTest();
return null;
}
private void fakeItTest() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Set<GiftSet> giftSets2 = new HashSet<>();
GiftSet gg = new GiftSet();
gg.setId(1111);
gg.setHoliday(new Holiday());
giftSets2.add(gg);
GiftSet gg2 = new GiftSet();
gg2.setId(1112);
gg2.setHoliday(new Holiday());
giftSets2.add(gg2);
Holiday holiday2 = new Holiday();
holiday2.setName("HolidaySet");
holiday2.setGiftSets(giftSets2);
String a = objectMapper.writeValueAsString(holiday2);
System.out.println(a);
}
#Data
public static class Holiday{
private String name;
private Set<GiftSet> giftSets;
}
#Data
public static class GiftSet {
private Integer id;
private Holiday holiday;
}