Skip to content

Commit

Permalink
Merge pull request #424 from taswartz/fix-rest-extra-property-error
Browse files Browse the repository at this point in the history
Fixing Rest Mapping failures when an extra property is included as a sibling to the root JSON element
  • Loading branch information
johnsully83 authored May 19, 2023
2 parents 3d6eebd + 4877c71 commit 36f013b
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 179 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.bullhorn</groupId>
<artifactId>sdk-rest</artifactId>
<version>1.4.59</version>
<version>1.4.60</version>
<packaging>jar</packaging>

<name>Bullhorn REST SDK</name>
Expand Down
239 changes: 120 additions & 119 deletions src/main/java/com/bullhornsdk/data/api/StandardBullhornData.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,10 @@ public class RestJsonConverter {

private static Logger log = LogManager.getLogger(RestJsonConverter.class);

private final ObjectMapper objectMapperWrapped;

private final ObjectMapper objectMapperStandard;
private final ObjectMapper objectMapper;

public RestJsonConverter() {
super();
this.objectMapperWrapped = createObjectMapperWithRootUnWrap();
this.objectMapperStandard = createObjectMapper();
this.objectMapper = createObjectMapper();
}

/*
Expand All @@ -37,10 +33,10 @@ public RestJsonConverter() {

/**
* Create the ObjectMapper that deserializes entity to json String.
*
*
* Registers the JodaModule to convert DateTime so-called epoch timestamp (number of milliseconds since January 1st, 1970,
* UTC)
*
*
* @return
*/
private ObjectMapper createObjectMapper() {
Expand All @@ -50,45 +46,9 @@ private ObjectMapper createObjectMapper() {
return mapper;
}

/**
* Creates the ObjectMapper that serializes json to entity. Wraps the root (most often "data").
*
* See @JsonRootName on the RestEntities
*
* @return
*/
private ObjectMapper createObjectMapperWithRootUnWrap() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
mapper.addHandler(new CustomDeserializationProblemHandler());


mapper.registerModule(new JodaModule());
return mapper;
}

/**
* Converts a jsonString to an object of type T. Unwraps from root, most often this means that the "data" tag is ignored and
* that the entity is created from within that data tag.
*
* @param jsonString
* the returned json from the api call.
* @param type
* the type to convert to
* @return a type T
*/
public <T> T jsonToEntityUnwrapRoot(String jsonString, Class<T> type) {
return jsonToEntity(jsonString, type, this.objectMapperWrapped);
}

public <T> T jsonToEntityDoNotUnwrapRoot(String jsonString, Class<T> type) {
return jsonToEntity(jsonString, type, this.objectMapperStandard);
}

public <T> T jsonToEntity(String jsonString, Class<T> type, ObjectMapper objectMapper) {

public <T> T jsonToEntity(String jsonString, Class<T> type) {
try {
return objectMapper.readValue(jsonString, type);
return this.objectMapper.readValue(jsonString, type);
} catch(JsonParseException e) {
throw new RestMappingException("Error mapping jsonString to " + type + ". jsonString = " + jsonString, e);
} catch(JsonMappingException e) {
Expand All @@ -100,15 +60,15 @@ public <T> T jsonToEntity(String jsonString, Class<T> type, ObjectMapper objectM

/**
* Takes a BullhornEntity and converts it to a String in json format.
*
*
* @param entity
* a BullhornEntity
* @return the jsonString
*/
public <T extends BullhornEntity> String convertEntityToJsonString(T entity) {
String jsonString = "";
try {
jsonString = objectMapperStandard.writeValueAsString(entity);
jsonString = this.objectMapper.writeValueAsString(entity);
} catch (JsonProcessingException e) {
String message = "Error deserializing entity of type" + entity.getClass() + " to jsonString.";
log.error(message, e);
Expand Down
22 changes: 11 additions & 11 deletions src/main/java/com/bullhornsdk/data/api/mock/MockDataLoader.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -232,46 +232,46 @@ public class MockDataLoader {

public void reloadEditHistoryResults() {
String jsonData = getFileData("edithistory/edithistory-data.txt");
EditHistoryListWrapper listWrapper = restJsonConverter.jsonToEntityDoNotUnwrapRoot(jsonData, EditHistoryListWrapper.class);
EditHistoryListWrapper listWrapper = restJsonConverter.jsonToEntity(jsonData, EditHistoryListWrapper.class);
this.editHistoryList = listWrapper.getData();
}

public void reloadEditHistoryFieldChangeResults() {
String jsonData = getFileData("edithistory/fieldchange-data.txt");
FieldChangeListWrapper listWrapper = restJsonConverter.jsonToEntityDoNotUnwrapRoot(jsonData, FieldChangeListWrapper.class);
FieldChangeListWrapper listWrapper = restJsonConverter.jsonToEntity(jsonData, FieldChangeListWrapper.class);
this.editHistoryFieldChangeList = listWrapper.getData();
}

public void reloadFastFindResults() {

String jsonData = getFileData("fastfind-data.txt");
FastFindListWrapper listWrapper = restJsonConverter.jsonToEntityDoNotUnwrapRoot(jsonData, FastFindListWrapper.class);
FastFindListWrapper listWrapper = restJsonConverter.jsonToEntity(jsonData, FastFindListWrapper.class);
this.fastFindResultList = listWrapper.getData();
}

public void reloadGetEventsResponses() {
String jsonData = getFileData("event-data.txt");
StandardGetEventsResponse eventsResponse = restJsonConverter.jsonToEntityDoNotUnwrapRoot(jsonData, StandardGetEventsResponse.class);
StandardGetEventsResponse eventsResponse = restJsonConverter.jsonToEntity(jsonData, StandardGetEventsResponse.class);
this.getEventsResponse = eventsResponse;
}

public void reloadGetLastRequestIdResponses() {
String jsonData = getFileData("lastrequestid-data.txt");
StandardGetLastRequestIdResponse response = restJsonConverter.jsonToEntityDoNotUnwrapRoot(jsonData, StandardGetLastRequestIdResponse.class);
StandardGetLastRequestIdResponse response = restJsonConverter.jsonToEntity(jsonData, StandardGetLastRequestIdResponse.class);
this.getLastRequestIdResponse = response;
}

public void reloadSettingsResults() {

String jsonData = getFileData("settings-data.txt");
Map<String, Object> resultData = restJsonConverter.jsonToEntityDoNotUnwrapRoot(jsonData, Map.class);
Map<String, Object> resultData = restJsonConverter.jsonToEntity(jsonData, Map.class);
this.settingsResultMap = resultData;
}

public void reloadSettingsObjectResults() {

String jsonData = getFileData("settings-data.txt");
Settings resultData = restJsonConverter.jsonToEntityDoNotUnwrapRoot(jsonData, Settings.class);
Settings resultData = restJsonConverter.jsonToEntity(jsonData, Settings.class);
this.settingsObjectResult = resultData;
}

Expand Down Expand Up @@ -328,7 +328,7 @@ public class MockDataLoader {

public void reloadPropertyOptionsResults() {
String jsonData = getFileData("propertyoptions-data.txt");
PropertyOptionsListWrapper listWrapper = restJsonConverter.jsonToEntityDoNotUnwrapRoot(jsonData, PropertyOptionsListWrapper.class);
PropertyOptionsListWrapper listWrapper = restJsonConverter.jsonToEntity(jsonData, PropertyOptionsListWrapper.class);
this.propertyOptionsResultList = listWrapper.getData();
}

Expand Down Expand Up @@ -366,7 +366,7 @@ public class MockDataLoader {
*/
private <T extends BullhornEntity> List<T> jsonStringToEntityList(String jsonData, Class<T> type) {

ListWrapper<T> listWrapper = restJsonConverter.jsonToEntityDoNotUnwrapRoot(jsonData, BullhornEntityInfo.getTypesListWrapperType(type));
ListWrapper<T> listWrapper = restJsonConverter.jsonToEntity(jsonData, BullhornEntityInfo.getTypesListWrapperType(type));

return listWrapper.getData();

Expand All @@ -381,7 +381,7 @@ public class MockDataLoader {
* @return
*/
private <T extends BullhornEntity> MetaData<T> jsonStringToMetaData(String jsonData) {
return restJsonConverter.jsonToEntityDoNotUnwrapRoot(jsonData, StandardMetaData.class);
return restJsonConverter.jsonToEntity(jsonData, StandardMetaData.class);
}

/**
Expand All @@ -391,7 +391,7 @@ public class MockDataLoader {
*/
private List<MockSearchField> jsonStringToMockSearchFields(String jsonData) {

MockSearchFieldWrapper fieldsWrapper = restJsonConverter.jsonToEntityDoNotUnwrapRoot(jsonData, MockSearchFieldWrapper.class);
MockSearchFieldWrapper fieldsWrapper = restJsonConverter.jsonToEntity(jsonData, MockSearchFieldWrapper.class);

return fieldsWrapper.getSearchFields();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.bullhornsdk.data.model.response.single;

import com.bullhornsdk.data.model.response.file.standard.StandardFileContent;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

import javax.annotation.Generated;
import java.util.HashMap;
import java.util.Map;

@JsonInclude(JsonInclude.Include.NON_NULL)
@Generated("com.googlecode.jsonschema2pojo")
@JsonPropertyOrder({ "File" })
public class StandardFileContentWrapper {

@JsonProperty("File")
private StandardFileContent file;

private Map<String, Object> additionalProperties = new HashMap<String, Object>();

@JsonProperty("File")
public StandardFileContent getFile() {
return this.file;
}

@JsonProperty("File")
public void setFile(StandardFileContent file) {
this.file = file;
}

@JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}

@JsonAnySetter
public void setAdditionalProperties(String name, Object value) {
this.additionalProperties.put(name, value);
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Wrapper {\ndata=");
builder.append(file);
builder.append(", \nadditionalProperties=");
builder.append(additionalProperties);
builder.append("\n}");
return builder.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.bullhornsdk.data.api.helper;

import com.bullhornsdk.data.exception.RestMappingException;
import com.bullhornsdk.data.model.entity.core.standard.JobSubmission;
import com.bullhornsdk.data.model.enums.BullhornEntityInfo;
import com.bullhornsdk.data.model.response.file.standard.StandardFileContent;
import com.bullhornsdk.data.model.response.list.ListWrapper;
import com.bullhornsdk.data.model.response.single.StandardFileContentWrapper;
import com.bullhornsdk.data.model.response.single.StandardWrapper;
import com.bullhornsdk.data.model.response.single.Wrapper;
import org.junit.Test;

import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class RestJsonConverterTest {
RestJsonConverter restJsonConverter = new RestJsonConverter();

private final String singleEntityJson = "{ \"data\": { \"id\": 1, \"status\":\"Approved\" }}";

private final String singleEntityJsonExtraProp = "{ " +
"\"total\": 1, " +
"\"data\": { \"id\": 1, \"status\":\"Approved\" }}";

private final String multipleEntityJson = "{ " +
"\"data\": [" +
"{ \"id\": 1, \"status\":\"Approved\" }, " +
"{ \"id\": 2, \"status\":\"Not Submitted\" }" +
"]}";

private final String multipleEntityJsonExtraProp = "{ " +
"\"total\": 2, " +
"\"data\": [" +
"{ \"id\": 1, \"status\":\"Approved\" }, " +
"{ \"id\": 2, \"status\":\"Not Submitted\" }" +
"]}";

private final String standardFileContentJson = "{ " +
"\"File\": { \"fileContent\": \"SomeContent\", \"name\":\"FileName\" }}";

private final String standardFileContentJsonExtraProp = "{ " +
"\"total\": 1, " +
"\"File\": { \"fileContent\": \"SomeContent\", \"name\":\"FileName\" }}";

@Test
public void jsonToEntityTest() {
Wrapper<JobSubmission> jobSubmissionWrapper = this.restJsonConverter.jsonToEntity(this.singleEntityJson, BullhornEntityInfo.getTypesWrapperType(JobSubmission.class));
assertNotNull(jobSubmissionWrapper);
assertNotNull(jobSubmissionWrapper.getData());
assertEquals(jobSubmissionWrapper.getData().getId(), new Integer(1));
assertEquals(jobSubmissionWrapper.getData().getStatus(), "Approved");
}

@Test(expected = RestMappingException.class)
public void jsonToEntityMultipleException() {
this.restJsonConverter.jsonToEntity(this.singleEntityJson, BullhornEntityInfo.getTypesListWrapperType(JobSubmission.class));
}

@Test
public void testJsonToEntityWithExtraParentProperty() {
StandardWrapper<JobSubmission> jobSubmissionWrapper = this.restJsonConverter.jsonToEntity(this.singleEntityJsonExtraProp, BullhornEntityInfo.getTypesWrapperType(JobSubmission.class));
assertNotNull(jobSubmissionWrapper);
assertNotNull(jobSubmissionWrapper.getData());
assertEquals(new Integer(1), jobSubmissionWrapper.getData().getId());
assertEquals("Approved", jobSubmissionWrapper.getData().getStatus());
assertEquals(1, jobSubmissionWrapper.getAdditionalProperties().get("total"));
}

@Test
public void testJsonListToEntitiesWithExtraParentProperty() {
ListWrapper<JobSubmission> jobSubmissionWrapper = this.restJsonConverter.jsonToEntity(this.multipleEntityJsonExtraProp, BullhornEntityInfo.getTypesListWrapperType(JobSubmission.class));
assertNotNull(jobSubmissionWrapper);
assertNotNull(jobSubmissionWrapper.getData());
assertEquals(new Integer(2), jobSubmissionWrapper.getTotal());
assertEquals(2, jobSubmissionWrapper.getData().size());
}

@Test
public void testJsonToMultipleEntity() {
ListWrapper<JobSubmission> jobSubmissionWrapper = this.restJsonConverter.jsonToEntity(this.multipleEntityJson, BullhornEntityInfo.getTypesListWrapperType(JobSubmission.class));
assertNotNull(jobSubmissionWrapper);
assertNotNull(jobSubmissionWrapper.getData());
List<JobSubmission> jobSubmissionList = jobSubmissionWrapper.getData();
assertEquals(new Integer(1), jobSubmissionList.get(0).getId());
assertEquals("Approved", jobSubmissionList.get(0).getStatus());
assertEquals(new Integer(2), jobSubmissionList.get(1).getId());
assertEquals("Not Submitted", jobSubmissionList.get(1).getStatus());
}

@Test(expected = RestMappingException.class)
public void testJsonToMultipleEntityWithSingleDataWrapper() {
this.restJsonConverter.jsonToEntity(this.multipleEntityJson, BullhornEntityInfo.getTypesWrapperType(JobSubmission.class));
}

@Test
public void testStandardFileContent() {
StandardFileContentWrapper wrapper = this.restJsonConverter.jsonToEntity(this.standardFileContentJson, StandardFileContentWrapper.class);
assertNotNull(wrapper);
assertNotNull(wrapper.getFile());
StandardFileContent file = wrapper.getFile();
assertEquals("SomeContent", file.getFileContent());
assertEquals("FileName", file.getName());
}

@Test
public void testStandardFileContentWithExtraProp() {
StandardFileContentWrapper wrapper = this.restJsonConverter.jsonToEntity(this.standardFileContentJsonExtraProp, StandardFileContentWrapper.class);
assertNotNull(wrapper);
assertNotNull(wrapper.getFile());
StandardFileContent file = wrapper.getFile();
assertEquals("SomeContent", file.getFileContent());
assertEquals("FileName", file.getName());
}
}

0 comments on commit 36f013b

Please sign in to comment.