Parsing JSON into custom POJO which has Map as one it's class member - jackson

I have below JSON which I want to parse into below bean classes.
{
"timeStamp": "123123123123",
"pznFlowFlag": "true",
"pznRequestFlag": "true",
"sessionId": "SampleSessionId",
"ipAddress": "172.148.0.1",
"offers": [
{
"111": {
"eep" : "44279",
"spl_ind" : "true"},
"121": {
"eep" : "44520",
"spl_ind" : "false"},
"333": {
"eep" : "45419",
"spl_ind" : "false" }
}]
}
Bean class 1
public class DistributedCookieBean {
#JsonProperty("timeStamp")
private String timeStamp;
#JsonProperty("pznFlowFlag")
private String pznFlowFlag;
#JsonProperty("pznRequestFlag")
private String pznRequestFlag;
#JsonProperty("sessionId")
private String sessionId;
#JsonProperty("ipAddress")
private String ipAddress;
#JsonProperty("offers")
private Map<String, OfferCookieBean> offers = new HashMap<String, OfferCookieBean>();
...<setters & getters>
}
Bean class 2
public class OfferCookieBean {
#JsonProperty("eep")
private String eep;
#JsonProperty("spl_ind")
private String spl_ind;
...<setters & getters>
}
Here I am not able to parse into the these Java POJOs using below code.
jsonObjMapper.readValue(jsonString, DistributedCookieBean.class);
Stacktrace:
org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.util.Map out of START_ARRAY token
at [Source: json.txt; line: 6, column: 31]
at org.codehaus.jackson.map.JsonMappingException.from(JsonMappingException.java:159)
at org.codehaus.jackson.map.deser.StdDeserializationContext.mappingException(StdDeserializationContext.java:192)
at org.codehaus.jackson.map.deser.MapDeserializer.deserialize(MapDeserializer.java:134)
at org.codehaus.jackson.map.deser.MapDeserializer.deserialize(MapDeserializer.java:23)
at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:135)
at org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.deserializeAndSet(SettableBeanProperty.java:221)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:391)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:287)
at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:1588)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1074)
at Test.main(Test.java:29)
Thanks in advance!

The error message points to the problem: the offers field is declared as a map while in JSON it is an array of maps.
Try to change Map<String, OfferCookieBean> offers = new HashMap<String, OfferCookieBean>() to List<Map<String, OfferCookieBean>> offers; and see what happens.

Related

Problem to deserialize json to java object of generic type with jackson 2.13.4.2

There's a message defined as below, note there're different implementations of the generic type M
// The message definition
#Value
#Builder(toBuilder = true)
#Jacksonized
public class MyMessage<M> {
#Builder.Default
Map<String, String> props = new HashMap<>();
M content;
}
// One implementation of the generic type M in MyMessage
class ContentType1 {
String name;
SomeSimplePojo pojo;
Map<String, String> contentProps;
}
Here's an example of above message:
{
"props": {
"trace-id": "3468f6022b749dbc"
},
"content": {
"name": "contentExample1",
"pojo": {
"field1": "val1",
"field2": "val2"
},
"contentProps": {
"/Count": "9",
"/Email": "someone#stackoverflow.com"
}
}
}
The message was deserialized basically with below code snippet, by com.fasterxml.jackson.* version 2.10.4. It worked fine before.
ObjectMapper objectMapper = new ObjectMapper();
MyMessage<String> rawMsg = objectMapper.readValue(jsonStr, new TypeReference<MyMessage<String>>() {});
// Here clazzOfM is the class of type M
MyMessage<M> convertedMsg = MyMessage.<M>builder().content(objectMapper.convertValue(rawMsg.getContent(), clazzOfM)).props(rawMsg.getProps()).build();
But recently, I upgraded com.fasterxml.jackson.databind to 2.13.4.2, all other com.fasterxml.jackson.* to 2.13.4. Then it failed at this line MyMessage<String> rawMsg = objectMapper.readValue(jsonStr, new TypeReference<MyMessage<String>>() {}); with Exception, which points to the generic type M, field content at column 52:
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
at [Source: (String)"{"props":{"trace-id":"3468f6022b749dbc"},"content":{"name":"contentExample1","pojo":{"field1":"val1","field2":"val2"},"contentProps":{"/Count":"9","/Email":"someone#stackoverflow.com"}}}"; line: 1, column: 52] (through reference chain: com.demo.example.MyMessage$MyMessageBuilder["content"])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1741)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1515)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1420)
at com.fasterxml.jackson.databind.DeserializationContext.extractScalarFromObject(DeserializationContext.java:932)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:62)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:11)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeSetAndReturn(MethodProperty.java:158)
at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.vanillaDeserialize(BuilderBasedDeserializer.java:293)
at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.deserialize(BuilderBasedDeserializer.java:217)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4674)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3629)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3612)
I tried to change the problematic line to MyMessage<M> rawMsg = objectMapper.readValue(jsonStr, new TypeReference<MyMessage<M>>() {});, no exception then, but I still have two questions.
Is the above change a correct approach to deserialize json text to MyMessage by jackson 2.13.4(.2)? What is the best practice to this kind of deserialization if the above isn't.
After the above change, I notice that the type of rawMsg.content is LinkedHashMap, it isn't M(CotentType1 in this test) as I expected. But the type of convertedMsg.content IS ContentType1 after executing this converting line MyMessage<M> convertedMsg = MyMessage.<M>builder().content(objectMapper.convertValue(rawMsg.getContent(), clazzOfM)).props(rawMsg.getProps()).build();.
I can't understand why the type of rawMsg.content is LinkedHashMap instead of ContentType1. Could someone help explain?

MockMvc POST returning 400 - expecting 201

I'm trying to write a controller unit test for a #PostMapping but am getting a failed test
Status expected:<201> but was:<400>
The controller works as expected in Postman so I know it actually works, but it would be nice to have a working unit test as well.
What am I doing wrong?
TEST
#Test
#DisplayName("CREATE NEW ENFORCEMENT ACTION")
void testCreateNewEnforcementAction() throws Exception {
EnforcementAction mockAction = new EnforcementAction();
mockAction.setSystemId(1289);
mockAction.setCurrentStaff("ralbritton");
mockAction.setCurrentStatus("NEEDED");
mockAction.setCreatedOn(LocalDateTime.now());
mockAction.setCreatedBy("ralbritton");
mockAction.setEaType("IF");
mockAction.setEaCode("CAP");
mockAction.setDeleted(false);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(mockAction);
mockMvc.perform(MockMvcRequestBuilders.post("/api/enforcementactions/action")
.contentType(MediaType.APPLICATION_JSON)
.content(json)
.characterEncoding("utf-8"))
.andExpect(status().isCreated()); //Have also tried this as .isOK() (didn't make a diff)
//.andReturn(); ///Added and removed this line to see if it made a differnce (it did not)
}
CONTROLLER BEING TESTED
#PostMapping("/api/enforcementactions/action")
public ResponseEntity<?> createNewEnforcementAction(#RequestBody EnforcementAction newAction) {
service.createEnforcementAction(newAction);
return new ResponseEntity<>(newAction, HttpStatus.CREATED);
}
MODEL
UPDATE: I'm adding in the model to show that there is not Bean Validation on fields
public class EnforcementAction {
private Integer eaId;
private Integer systemId;
private String alternateNumber;
private String systemName;
private Integer tenschdId;
private String currentStaff;
private String currentStatus;
private LocalDate dateActionIssued;
private LocalDate dateActionClosed;
private boolean deleted;
private LocalDateTime createdOn;
private String createdBy;
private LocalDateTime modifiedOn;
private String lastModifiedBy;
private String eaType;
private String eaCode;
private String comment;
private Long daysSinceCreate;
private List<EaStaffHistory> staffAssigned = new ArrayList<>();
private List<EaDocStatusHistory> documentStatus = new ArrayList<>();
private List<EaComments> eaComments = new ArrayList<>();
/** Constructors */
public EnforcementAction() {
}
public EnforcementAction(Integer eaId, Integer systemId, String systemName, Integer tenschdId,
String currentStaff, String currentStatus, Long daysSinceCreate,
String createdBy, String lastModifiedBy, LocalDate dateActionIssued, LocalDate dateActionClosed,
String eaType, String eaCode, LocalDateTime createdOn) {
this.eaId = eaId;
this.systemId = systemId;
this.tenschdId = tenschdId;
this.systemName = systemName;
this.currentStaff = currentStaff;
this.currentStatus = currentStatus;
this.createdBy = createdBy;
this.lastModifiedBy = lastModifiedBy;
this.dateActionClosed = dateActionClosed;
this.dateActionIssued = dateActionIssued;
this.eaType = eaType;
this.eaCode = eaCode;
this.daysSinceCreate = daysSinceCreate;
this.createdOn = createdOn;
}
...getters and setters....
POSTMAN showing successful post:
EDIT: I've updated the OP code to reflect current state. Still having the same issue though.
The reason for the 400 is how you send your payload to your controller. You are not serializing the Java object to JSON, but use the .toString() representation of it:
.content(String.valueOf(mockAction)))
Either make use of the ObjectMapper or prepare a custom JSON string:
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(mockAction);
mockMvc.perform(MockMvcRequestBuilders
.post("/api/enforcementactions/action")
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isCreated());
OK so I finally figured out my problem and I'm posting it here in case someone else has the same issue. While #Rieckpil was correct in all his suggestions (and I will mark his answer as correct) the other problem I was having was in my mockAction object. I had:
mockAction.setCreatedOn(LocalDateTime.now())
Even though createdOn is of type LocalDateTime it was getting deconstructed in the body to look like this:
"createdOn": {
"dayOfWeek": "WEDNESDAY",
"dayOfYear": 204,
"month": "JULY",
"year": 2020,
"dayOfMonth": 22,
"hour": 12,
"minute": 43,
"monthValue": 7,
"nano": 839000000,
"second": 10,
"chronology": {
"id": "ISO",
"calendarType": "iso8601"
}
}
When I passed this as the createdOn variable into Postman I was able to get a meaningful error .HttpMessageNotReadableException: JSON parse error: Expected array or string.; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Expected array or string. at [Source: (PushbackInputStream); line: 12, column: 21] (through reference chain: gov.deq.utah.enforcementactions.models.enforcementActions.EnforcementAction["createdOn"])
The test passed when I removed this. I kept all other suggestions provided.

Spring Batch - Unable to deserialize the execution context - OffsetDateTime - cannot deserialize

I'm trying to create a spring batch job with multiples steps and passing object from step to step.
To do this I use ExecutionContext that i promoted from step to job context.
At first run, no problem data goes right from step to step
At next runs, I get the error :
"Unable to deserialize the execution context" Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of java.time.OffsetDateTime (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
I write context in a ItemWriter like so :
#Override
public void write(List<? extends Employee> items) throws Exception {
ExecutionContext stepContext = this.stepExecution.getExecutionContext();
List<Employee> e = new ArrayList<Employee>();
e.addAll(items);
stepContext.put("someKey", e);
}
And read it back in a ItemReader (from another step) with :
#BeforeStep
public void retrieveInterstepData(StepExecution stepExecution) {
JobExecution jobExecution = stepExecution.getJobExecution();
ExecutionContext jobContext = jobExecution.getExecutionContext();
this.someObject = (List<Employee>) jobContext.get("someKey");
}
I check spring database context and my dates (LocalDate, OffsetDateTime, ...) are store like :
"LocalDate": {
"year": 2019,
"month": "OCTOBER",
"dayOfMonth": 30,
"monthValue": 10,
"era": ["java.time.chrono.IsoEra", "CE"],
"dayOfWeek": "WEDNESDAY",
"dayOfYear": 303,
"leapYear": false,
"chronology": {
"id": "ISO",
"calendarType": "iso8601"
}
}
"OffsetDateTime": {
"offset": {
"totalSeconds": 0,
"id": "Z",
"rules": {
"fixedOffset": true,
"transitionRules": ["java.util.Collections$UnmodifiableRandomAccessList", []],
"transitions": ["java.util.Collections$UnmodifiableRandomAccessList", []]
}
},
"month": "OCTOBER",
"year": 2019,
"dayOfMonth": 28,
"hour": 13,
"minute": 42,
"monthValue": 10,
"nano": 511651000,
"second": 36,
"dayOfWeek": "MONDAY",
"dayOfYear": 301
}
I guess it's jackson's choice to store it like that (I custom nothing)
But it seems that jackson can't read it's own format at next run ?!
My stubs are generated with from swagger with "swagger-codegen-maven-plugin" and configOptions/dateLibrary=java8 so I can't change them.
I tried to add
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
And
#PostConstruct
public void init() {
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
In the #SpringBootApplication
no change
Any ideas ? Either to store dates more simply like "2019-11-04" or make jackson read it's own format ?
Your object mapper should be set on the Jackson2ExecutionContextStringSerializer used by the job repository. You can extend DefaultBatchConfigurer and override createJobRepository:
#Bean
public JobRepository createJobRepository() throws Exception {
ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
Jackson2ExecutionContextStringSerializer defaultSerializer = new Jackson2ExecutionContextStringSerializer();
defaultSerializer.setObjectMapper(objectMapper);
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
factory.setSerializer(defaultSerializer);
factory.afterPropertiesSet();
return factory.getObject();
}
EDIT :
My bad I just saw that I have a
#Bean
public BatchConfigurer batchConfigurer(#Qualifier("batchDataSource") DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource);
}
That provide 2 batchConfigurer to spring.
Thanks !
ORIGINAL :
Thanks it seems promising.
But I dont find where to extends and use it, on which class.
I have a Batch Class configuration :
#Configuration
#EnableConfigurationProperties(BatchProperties.class)
public class BatchDatabaseConfiguration {
#Value("${spring.datasource.driver-class-name}")
private String driverClassName;
#Value("${spring.datasource.url}")
private String dbURL;
#Bean("batchDataSource")
public DataSource batchDataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(dbURL);
return dataSource;
}
#Bean
public BatchConfigurer batchConfigurer(#Qualifier("batchDataSource") DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource);
}
#Bean(name = "batchTransactionManager")
public PlatformTransactionManager batchTransactionManager(#Qualifier("batchDataSource") DataSource dataSource) {
DataSourceTransactionManager tm = new DataSourceTransactionManager();
tm.setDataSource(dataSource);
return tm;
}
}
And a Class with Job's definition :
#Configuration
#EnableBatchProcessing
public class ExtractionJobConfiguration {
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Autowired
private StepBuilderFactory stepBuilderFactory;
#Bean
public Job creationJob() {
...
}
[...]
}
And the main :
#EntityScan(basePackages = { "..." })
#SpringBootApplication
#EnableAsync
public class App {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(App.class, args);
}
What do you think ?
I also read that Spring Batch 4.2.0+ allow for customisation of ObjectMapper in Jackson2ExecutionContextStringSerializer (https://jira.spring.io/browse/BATCH-2828)
Is that what you propose ? (I don't find other information)

Using jackson mixin class for a list of objects

I'm having a problem deserializing the following json
{
"GrpHdr": {
"MsgId": "Message-1",
"CreDtTm": "2018-03-02T10:15:30+01:00[Europe/Paris]",
"NbOfTxs": "1",
"InitgPty": {
"Nm": "Remitter"
}
},
"PmtInf": [
{
"PmtInfId": "1"
},
{
"PmtInfId": "2"
}
]
}
I have created a MixIn class:
public abstract class CustomerCreditTransferInitiationMixIn {
public PaymentInstructions paymentInstructions;
#JsonCreator
public CustomerCreditTransferInitiationMixIn(
#JsonProperty("GrpHdr") GroupHeader GrpHdr,
#JsonProperty("PmtInf") List<PaymentInstruction> PmtInf
) {
this.paymentInstructions = PaymentInstructions.valueOf(PmtInf);
}
#JsonProperty("GrpHdr")
abstract GroupHeader getGroupHeader();
#JsonProperty("PmtInf")
abstract List<PaymentInstruction> getPaymentInstructions();
}
I'm having no trouble deserializing the group header in this case. Mapping different names. But in the PmtInf case I get confused. It is a list that I want to deserialize to a List of PaymentInstructions. But PmtInf is a paymentistruction.
I have created a test:
#Test
public void JacksonMixinAnnotationTestJsonIsoFileFromTester() throws JsonProcessingException, Throwable {
CustomerCreditTransferInitiation customerCreditTransferInitiation;
String jsonFile = "testWithShortNames";
InputStream inputStream = new ClassPathResource(jsonFile + ".json").getInputStream();
ObjectMapper objectMapper = buildMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.registerModule(new JavaTimeModule());
objectMapper.addMixIn(CustomerCreditTransferInitiation.class, CustomerCreditTransferInitiationMixIn.class);
objectMapper.addMixIn(GroupHeader.class, GroupHeaderMixIn.class);
objectMapper.addMixIn(PaymentInstruction.class, PaymentInstructionMixIn.class);
objectMapper.addMixIn(PartyIdentification.class, PartyIdentificationMixIn.class);
customerCreditTransferInitiation = objectMapper.readValue(inputStream, CustomerCreditTransferInitiation.class);
//GroupHeader
Assert.assertNotNull(customerCreditTransferInitiation.getGroupHeader());
Assert.assertNotNull(customerCreditTransferInitiation.getGroupHeader().getMessageId());
Assert.assertNotNull(customerCreditTransferInitiation.getGroupHeader().getCreationDateTime());
Assert.assertNotNull(customerCreditTransferInitiation.getGroupHeader().getNumberOfTransactions());
Assert.assertNotNull(customerCreditTransferInitiation.getGroupHeader().getInitiatingParty());
Assert.assertNotNull(customerCreditTransferInitiation.getGroupHeader().getInitiatingParty().getName());
//PaymentInstructions
Assert.assertNotNull(customerCreditTransferInitiation.getPaymentInstructions());}
Getting the following error:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "PmtInfId" (class
com.seb.payment.iso.domain.PaymentInstruction), not marked as
ignorable (19 known properties: "paymentInformationId",
"paymentMethod", "created", "paymentTypeInformation", "controlSum",
"debtorAgent", "instructionForDebtorAgent", "numberOfTransactions",
"requestExecutionTime", "debtorAccount", "creditTransferTransactions",
"debtorAgentAccount", "batchBooking", "poolingAdjustmentDate",
"ultimateDebtor", "chargeBearerType", "debtor", "chargesAccount",
"chargesAccountAgent"]) at [Source: UNKNOWN; line: -1, column: -1]
(through reference chain:
com.seb.payment.iso.domain.CustomerCreditTransferInitiation["PmtInf"]->com.seb.payment.iso.domain.PaymentInstruction["PmtInfId"])
In our case we have implemented our own deserializers in abstract iterable.
On:
ObjectReader objectReader = ObjectMapperFactory.instance().readerFor(this.itemClass);
MixedIn classes are lost

Set field to a shared value

I have a class that looks like this:
class Data {
#JsonCreator
public Data(#JsonProperty("string") String s, Widget w) {
string = s;
widget = w;
}
String string;
Widget widget;
}
I want to deserialize it from this from JSON like
{
"string": "string value"
}
When deserializing, I want to set widget to a shared instance. I have that instance when I create the object mapper, but I can not see how to tell Jackson to use this instance.
I see JsonDeserialize.getNullValue and getEmptyValue, but those look like they are for handling
{
"string": "string value", "widget": null
}
which is not the JSON that I have.
You could try #JacksonInject:
public class Data {
#JacksonInject
public Widget widget;
...
}
And then use as follows:
Widget widget = ...
InjectableValues injectable = new InjectableValues.Std().addValue(Widget.class, widget);
Data data = new ObjectMapper().reader(injectable).forType(Data.class).readValue(json);