Update api json mapping to include null & empty values

This commit is contained in:
2023-05-25 10:22:04 -05:00
parent 76b102b811
commit 515e04ecfe
4 changed files with 72 additions and 12 deletions

View File

@ -28,6 +28,7 @@ import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
@ -62,11 +63,29 @@ public class JsonUtils
** **
*******************************************************************************/ *******************************************************************************/
public static String toJson(Object object) public static String toJson(Object object)
{
return (toJson(object, null));
}
/*******************************************************************************
** Serialize any object into a JSON String - with customizations on the Jackson
** ObjectMapper.
**
** Internally using jackson - so jackson annotations apply!
**
*******************************************************************************/
public static String toJson(Object object, Consumer<ObjectMapper> objectMapperCustomizer)
{ {
try try
{ {
ObjectMapper mapper = newObjectMapper(); ObjectMapper mapper = newObjectMapper();
String jsonResult = mapper.writeValueAsString(object); if(objectMapperCustomizer != null)
{
objectMapperCustomizer.accept(mapper);
}
String jsonResult = mapper.writeValueAsString(object);
return (jsonResult); return (jsonResult);
} }
catch(JsonProcessingException e) catch(JsonProcessingException e)

View File

@ -28,6 +28,7 @@ import java.math.BigDecimal;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.kingsrook.qqq.backend.core.BaseTest; import com.kingsrook.qqq.backend.core.BaseTest;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
@ -38,6 +39,7 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
@ -68,6 +70,24 @@ class JsonUtilsTest extends BaseTest
/*******************************************************************************
**
*******************************************************************************/
@Test
public void test_toJsonQRecordInputWithNullValues()
{
QRecord qRecord = getQRecord();
String json = JsonUtils.toJson(qRecord, objectMapper ->
{
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
});
assertThat(json).contains("""
"values":{"foo":"Foo","bar":3.14159,"baz":null}""");
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -213,6 +233,7 @@ class JsonUtilsTest extends BaseTest
qRecord.setValues(values); qRecord.setValues(values);
values.put("foo", "Foo"); values.put("foo", "Foo");
values.put("bar", new BigDecimal("3.14159")); values.put("bar", new BigDecimal("3.14159"));
values.put("baz", null);
return qRecord; return qRecord;
} }

View File

@ -33,6 +33,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.kingsrook.qqq.api.actions.ApiImplementation; import com.kingsrook.qqq.api.actions.ApiImplementation;
import com.kingsrook.qqq.api.actions.GenerateOpenApiSpecAction; import com.kingsrook.qqq.api.actions.GenerateOpenApiSpecAction;
import com.kingsrook.qqq.api.model.APILog; import com.kingsrook.qqq.api.model.APILog;
@ -297,7 +298,7 @@ public class QJavalinApiHandler
} }
context.contentType(ContentType.APPLICATION_JSON); context.contentType(ContentType.APPLICATION_JSON);
context.result(JsonUtils.toJson(rs)); context.result(toJson(rs));
} }
@ -312,7 +313,7 @@ public class QJavalinApiHandler
rs.put("currentVersion", apiInstanceMetaData.getCurrentVersion().toString()); rs.put("currentVersion", apiInstanceMetaData.getCurrentVersion().toString());
context.contentType(ContentType.APPLICATION_JSON); context.contentType(ContentType.APPLICATION_JSON);
context.result(JsonUtils.toJson(rs)); context.result(toJson(rs));
} }
@ -655,7 +656,7 @@ public class QJavalinApiHandler
Map<String, Serializable> outputRecord = ApiImplementation.get(apiInstanceMetaData, version, tableApiName, primaryKey); Map<String, Serializable> outputRecord = ApiImplementation.get(apiInstanceMetaData, version, tableApiName, primaryKey);
QJavalinAccessLogger.logEndSuccess(); QJavalinAccessLogger.logEndSuccess();
String resultString = JsonUtils.toJson(outputRecord); String resultString = toJson(outputRecord);
context.result(resultString); context.result(resultString);
storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(resultString)); storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(resultString));
} }
@ -668,6 +669,21 @@ public class QJavalinApiHandler
/*******************************************************************************
** Define standard way we'll make JSON objects for the API.
**
** Specifically, changes QQQ's default to include null values
*******************************************************************************/
private static String toJson(Object object)
{
return JsonUtils.toJson(object, mapper ->
{
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
});
}
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
@ -880,7 +896,7 @@ public class QJavalinApiHandler
Map<String, Serializable> output = ApiImplementation.query(apiInstanceMetaData, version, tableApiName, context.queryParamMap()); Map<String, Serializable> output = ApiImplementation.query(apiInstanceMetaData, version, tableApiName, context.queryParamMap());
QJavalinAccessLogger.logEndSuccess(logPair("recordCount", () -> ((List<?>) output.get("records")).size()), QJavalinAccessLogger.logPairIfSlow("filter", filter, SLOW_LOG_THRESHOLD_MS)); QJavalinAccessLogger.logEndSuccess(logPair("recordCount", () -> ((List<?>) output.get("records")).size()), QJavalinAccessLogger.logPairIfSlow("filter", filter, SLOW_LOG_THRESHOLD_MS));
String resultString = JsonUtils.toJson(output); String resultString = toJson(output);
context.result(resultString); context.result(resultString);
storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(resultString)); storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(resultString));
} }
@ -911,7 +927,7 @@ public class QJavalinApiHandler
QJavalinAccessLogger.logEndSuccess(); QJavalinAccessLogger.logEndSuccess();
context.status(HttpStatus.Code.CREATED.getCode()); context.status(HttpStatus.Code.CREATED.getCode());
String resultString = JsonUtils.toJson(outputRecord); String resultString = toJson(outputRecord);
context.result(resultString); context.result(resultString);
storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(resultString)); storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(resultString));
} }
@ -941,7 +957,7 @@ public class QJavalinApiHandler
List<Map<String, Serializable>> response = ApiImplementation.bulkInsert(apiInstanceMetaData, version, tableApiName, context.body()); List<Map<String, Serializable>> response = ApiImplementation.bulkInsert(apiInstanceMetaData, version, tableApiName, context.body());
context.status(HttpStatus.Code.MULTI_STATUS.getCode()); context.status(HttpStatus.Code.MULTI_STATUS.getCode());
String resultString = JsonUtils.toJson(response); String resultString = toJson(response);
context.result(resultString); context.result(resultString);
storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(resultString)); storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(resultString));
} }
@ -972,7 +988,7 @@ public class QJavalinApiHandler
QJavalinAccessLogger.logEndSuccess(logPair("recordCount", response.size())); QJavalinAccessLogger.logEndSuccess(logPair("recordCount", response.size()));
context.status(HttpStatus.Code.MULTI_STATUS.getCode()); context.status(HttpStatus.Code.MULTI_STATUS.getCode());
String resultString = JsonUtils.toJson(response); String resultString = toJson(response);
context.result(resultString); context.result(resultString);
storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(resultString)); storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(resultString));
} }
@ -1003,7 +1019,7 @@ public class QJavalinApiHandler
QJavalinAccessLogger.logEndSuccess(logPair("recordCount", response.size())); QJavalinAccessLogger.logEndSuccess(logPair("recordCount", response.size()));
context.status(HttpStatus.Code.MULTI_STATUS.getCode()); context.status(HttpStatus.Code.MULTI_STATUS.getCode());
String resultString = JsonUtils.toJson(response); String resultString = toJson(response);
context.result(resultString); context.result(resultString);
storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(resultString)); storeApiLog(apiLog.withStatusCode(context.statusCode()).withResponseBody(resultString));
} }
@ -1176,7 +1192,7 @@ public class QJavalinApiHandler
/////////////////////////// ///////////////////////////
} }
String responseBody = JsonUtils.toJson(Map.of("error", errorMessage)); String responseBody = toJson(Map.of("error", errorMessage));
context.result(responseBody); context.result(responseBody);
if(apiLog != null) if(apiLog != null)

View File

@ -202,6 +202,8 @@ class QJavalinApiHandlerTest extends BaseTest
assertEquals(1, jsonObject.getInt("id")); assertEquals(1, jsonObject.getInt("id"));
assertEquals("Homer", jsonObject.getString("firstName")); assertEquals("Homer", jsonObject.getString("firstName"));
assertEquals("Simpson", jsonObject.getString("lastName")); assertEquals("Simpson", jsonObject.getString("lastName"));
assertTrue(jsonObject.isNull("noOfShoes"));
assertFalse(jsonObject.has("someNonField"));
} }
@ -376,6 +378,8 @@ class QJavalinApiHandlerTest extends BaseTest
assertEquals(1, jsonObject.getInt("id")); assertEquals(1, jsonObject.getInt("id"));
assertEquals("Homer", jsonObject.getString("firstName")); assertEquals("Homer", jsonObject.getString("firstName"));
assertEquals("Simpson", jsonObject.getString("lastName")); assertEquals("Simpson", jsonObject.getString("lastName"));
assertTrue(jsonObject.isNull("noOfShoes"));
assertFalse(jsonObject.has("someNonField"));
} }
@ -959,7 +963,7 @@ class QJavalinApiHandlerTest extends BaseTest
assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(2).getInt("statusCode")); assertEquals(HttpStatus.BAD_REQUEST_400, jsonArray.getJSONObject(2).getInt("statusCode"));
assertEquals("Error updating Person: Missing value in primary key field", jsonArray.getJSONObject(2).getString("error")); assertEquals("Error updating Person: Missing value in primary key field", jsonArray.getJSONObject(2).getString("error"));
assertFalse(jsonArray.getJSONObject(2).has("id")); assertTrue(jsonArray.getJSONObject(2).isNull("id"));
assertEquals(HttpStatus.NOT_FOUND_404, jsonArray.getJSONObject(3).getInt("statusCode")); assertEquals(HttpStatus.NOT_FOUND_404, jsonArray.getJSONObject(3).getInt("statusCode"));
assertEquals("Error updating Person: No record was found to update for Id = 256", jsonArray.getJSONObject(3).getString("error")); assertEquals("Error updating Person: No record was found to update for Id = 256", jsonArray.getJSONObject(3).getString("error"));