BAEL-3326, "Optimizing JSON Schema for production use":

Added domain classes and JUnit tests.
This commit is contained in:
Karsten Silz 2020-09-05 18:46:57 +01:00
parent 0bb1851089
commit a7dc3199ad
10 changed files with 1637 additions and 0 deletions

View File

@ -45,6 +45,11 @@
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>javax.json.bind</groupId>
<artifactId>javax.json.bind-api</artifactId>

View File

@ -0,0 +1,123 @@
package com.baeldung.jsonoptimization;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Customer {
private long id;
private String firstName;
private String lastName;
private String street;
private String postalCode;
private String city;
private String state;
private String phoneNumber;
private String email;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public int hashCode() {
return Objects.hash(city, email, firstName, id, lastName, phoneNumber, postalCode, state, street);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Customer)) {
return false;
}
Customer other = (Customer) obj;
return Objects.equals(city, other.city) && Objects.equals(email, other.email) && Objects.equals(firstName, other.firstName) && id == other.id && Objects.equals(lastName, other.lastName) && Objects.equals(phoneNumber, other.phoneNumber)
&& Objects.equals(postalCode, other.postalCode) && Objects.equals(state, other.state) && Objects.equals(street, other.street);
}
@Override
public String toString() {
return "Customer [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", street=" + street + ", postalCode=" + postalCode + ", city=" + city + ", state=" + state + ", phoneNumber=" + phoneNumber + ", email=" + email + "]";
}
public static Customer[] fromMockFile() throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
InputStream jsonFile = new FileInputStream("src/test/resources/json_optimization_mock_data.json");
Customer[] feedback = objectMapper.readValue(jsonFile, Customer[].class);
return feedback;
}
}

View File

@ -0,0 +1,44 @@
package com.baeldung.jsonoptimization;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
public class CustomerDeserializer extends StdDeserializer<Customer> {
private static final long serialVersionUID = 1L;
public CustomerDeserializer() {
this(null);
}
public CustomerDeserializer(Class<Customer> t) {
super(t);
}
@Override
public Customer deserialize(JsonParser parser, DeserializationContext deserializer) throws IOException {
Customer feedback = new Customer();
ObjectCodec codec = parser.getCodec();
JsonNode node = codec.readTree(parser);
feedback.setId(node.get(0).asLong());
feedback.setFirstName(node.get(1).asText());
feedback.setLastName(node.get(2).asText());
feedback.setStreet(node.get(3).asText());
feedback.setPostalCode(node.get(4).asText());
feedback.setCity(node.get(5).asText());
feedback.setState(node.get(6).asText());
JsonNode phoneNumber = node.get(7);
feedback.setPhoneNumber(phoneNumber.isNull() ? null : phoneNumber.asText());
JsonNode email = node.get(8);
feedback.setEmail(email.isNull() ? null : email.asText());
return feedback;
}
}

View File

@ -0,0 +1,36 @@
package com.baeldung.jsonoptimization;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CustomerNoNull extends Customer {
@Override
public String toString() {
return "CustomerNoNull [toString()=" + super.toString() + "]";
}
public static CustomerNoNull[] fromCustomers(Customer[] customers) {
CustomerNoNull[] feedback = new CustomerNoNull[customers.length];
for(int i = 0; i < customers.length; i++) {
Customer aCustomer = customers[i];
CustomerNoNull newOne = new CustomerNoNull();
newOne.setId(aCustomer.getId());
newOne.setFirstName(aCustomer.getFirstName());
newOne.setLastName(aCustomer.getLastName());
newOne.setStreet(aCustomer.getStreet());
newOne.setCity(aCustomer.getCity());
newOne.setPostalCode(aCustomer.getPostalCode());
newOne.setState(aCustomer.getState());
newOne.setPhoneNumber(aCustomer.getPhoneNumber());
newOne.setEmail(aCustomer.getEmail());
feedback[i] = newOne;
}
return feedback;
}
}

View File

@ -0,0 +1,34 @@
package com.baeldung.jsonoptimization;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
public class CustomerSerializer extends StdSerializer<Customer> {
private static final long serialVersionUID = 1L;
public CustomerSerializer() {
this(null);
}
public CustomerSerializer(Class<Customer> t) {
super(t);
}
@Override
public void serialize(Customer customer, JsonGenerator jsonGenerator, SerializerProvider serializer) throws IOException {
jsonGenerator.writeStartArray();
jsonGenerator.writeNumber(customer.getId());
jsonGenerator.writeString(customer.getFirstName());
jsonGenerator.writeString(customer.getLastName());
jsonGenerator.writeString(customer.getStreet());
jsonGenerator.writeString(customer.getPostalCode());
jsonGenerator.writeString(customer.getCity());
jsonGenerator.writeString(customer.getState());
jsonGenerator.writeString(customer.getPhoneNumber());
jsonGenerator.writeString(customer.getEmail());
jsonGenerator.writeEndArray();
}
}

View File

@ -0,0 +1,138 @@
package com.baeldung.jsonoptimization;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
public class CustomerShortNames {
private long id;
@JsonProperty("f")
private String firstName;
@JsonProperty("l")
private String lastName;
@JsonProperty("s")
private String street;
@JsonProperty("p")
private String postalCode;
@JsonProperty("c")
private String city;
@JsonProperty("a")
private String state;
@JsonProperty("o")
private String phoneNumber;
@JsonProperty("e")
private String email;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public int hashCode() {
return Objects.hash(city, email, firstName, id, lastName, phoneNumber, postalCode, state, street);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof CustomerShortNames)) {
return false;
}
CustomerShortNames other = (CustomerShortNames) obj;
return Objects.equals(city, other.city) && Objects.equals(email, other.email) && Objects.equals(firstName, other.firstName) && id == other.id && Objects.equals(lastName, other.lastName) && Objects.equals(phoneNumber, other.phoneNumber)
&& Objects.equals(postalCode, other.postalCode) && Objects.equals(state, other.state) && Objects.equals(street, other.street);
}
@Override
public String toString() {
return "CustomerWithShorterAttributes [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", street=" + street + ", postalCode=" + postalCode + ", city=" + city + ", state=" + state + ", phoneNumber=" + phoneNumber + ", email=" + email
+ "]";
}
public static CustomerShortNames[] fromCustomers(Customer[] customers) {
CustomerShortNames[] feedback = new CustomerShortNames[customers.length];
for(int i = 0; i < customers.length; i++) {
Customer aCustomer = customers[i];
CustomerShortNames newOne = new CustomerShortNames();
newOne.setId(aCustomer.getId());
newOne.setFirstName(aCustomer.getFirstName());
newOne.setLastName(aCustomer.getLastName());
newOne.setStreet(aCustomer.getStreet());
newOne.setCity(aCustomer.getCity());
newOne.setPostalCode(aCustomer.getPostalCode());
newOne.setState(aCustomer.getState());
newOne.setPhoneNumber(aCustomer.getPhoneNumber());
newOne.setEmail(aCustomer.getEmail());
feedback[i] = newOne;
}
return feedback;
}
}

View File

@ -0,0 +1,38 @@
package com.baeldung.jsonoptimization;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CustomerShortNamesNoNull extends CustomerShortNames {
@Override
public String toString() {
return "CustomerShortNamesNoNull [toString()=" + super.toString() + "]";
}
public static CustomerShortNamesNoNull[] fromCustomers(Customer[] customers) {
CustomerShortNamesNoNull[] feedback = new CustomerShortNamesNoNull[customers.length];
for(int i = 0; i < customers.length; i++) {
Customer aCustomer = customers[i];
CustomerShortNamesNoNull newOne = new CustomerShortNamesNoNull();
newOne.setId(aCustomer.getId());
newOne.setFirstName(aCustomer.getFirstName());
newOne.setLastName(aCustomer.getLastName());
newOne.setStreet(aCustomer.getStreet());
newOne.setCity(aCustomer.getCity());
newOne.setPostalCode(aCustomer.getPostalCode());
newOne.setState(aCustomer.getState());
newOne.setPhoneNumber(aCustomer.getPhoneNumber());
newOne.setEmail(aCustomer.getEmail());
feedback[i] = newOne;
}
return feedback;
}
}

View File

@ -0,0 +1,74 @@
package com.baeldung.jsonoptimization;
import java.util.Objects;
public class CustomerSlim {
private long id;
private String name;
private String address;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public int hashCode() {
return Objects.hash(address, id, name);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof CustomerSlim)) {
return false;
}
CustomerSlim other = (CustomerSlim) obj;
return Objects.equals(address, other.address) && id == other.id && Objects.equals(name, other.name);
}
@Override
public String toString() {
return "CustomerSlim [id=" + id + ", name=" + name + ", address=" + address + "]";
}
public static CustomerSlim[] fromCustomers(Customer[] customers) {
CustomerSlim[] feedback = new CustomerSlim[customers.length];
for(int i = 0; i < customers.length; i++) {
Customer aCustomer = customers[i];
CustomerSlim newOne = new CustomerSlim();
newOne.setId(aCustomer.getId());
newOne.setName(aCustomer.getFirstName() + " " + aCustomer.getLastName());
newOne.setAddress(aCustomer.getStreet() + ", " + aCustomer.getCity() + " " + aCustomer.getState() + " " + aCustomer.getPostalCode());
feedback[i] = newOne;
}
return feedback;
}
}

View File

@ -0,0 +1,145 @@
package com.baeldung.jsonoptimization;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.zip.GZIPOutputStream;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.module.SimpleModule;
class JsonOptimizationUnitTest {
private static final String TEST_LABEL_DEFAULT_JSON = "Default JSON";
private static final String TEST_LABEL_DEFAULT_JSON_NO_NULL = "Default JSON without null";
private static final String TEST_LABEL_SHORTER_ATTRIBUTE_NAMES = "Shorter Attribute Names";
private static final String TEST_LABEL_SHORTER_ATTRIBUTE_NAMES_NO_NULL = "Shorter Attribute Names without null";
private static final String TEST_LABEL_CUSTOM_SERIALIZER = "Custom Serializer";
private static final String TEST_LABEL_SLIM_CUSTOMER = "Slim customer";
private static DecimalFormat LENGTH_FORMATTER = new DecimalFormat("###,###,###");
private static Customer[] customers;
private ObjectMapper mapper;
@BeforeAll
static void setUpOnce() throws Exception {
customers = Customer.fromMockFile();
}
@BeforeEach
void setUp() {
mapper = new ObjectMapper();
}
@Test
void testSetUp() {
assertEquals(1000, customers.length, "There should be a 1000 customers");
}
@Test
void testDefaultJson() throws IOException {
printBanner(TEST_LABEL_DEFAULT_JSON);
byte[] plainJson = createPlainJson(TEST_LABEL_DEFAULT_JSON, customers);
compressJson(TEST_LABEL_DEFAULT_JSON, plainJson);
}
@Test
void testDefaultNoNull() throws IOException {
printBanner(TEST_LABEL_DEFAULT_JSON_NO_NULL);
CustomerNoNull[] defaultNoNull = CustomerNoNull.fromCustomers(customers);
byte[] plainJson = createPlainJson(TEST_LABEL_DEFAULT_JSON_NO_NULL, defaultNoNull);
compressJson(TEST_LABEL_DEFAULT_JSON_NO_NULL, plainJson);
}
@Test
void testShorterAttributes() throws IOException {
printBanner(TEST_LABEL_SHORTER_ATTRIBUTE_NAMES);
CustomerShortNames[] shorterOnes = CustomerShortNames.fromCustomers(customers);
byte[] shorterJson = createPlainJson(TEST_LABEL_SHORTER_ATTRIBUTE_NAMES, shorterOnes);
compressJson(TEST_LABEL_SHORTER_ATTRIBUTE_NAMES, shorterJson);
}
@Test
void testShorterAttributesNoNull() throws IOException {
printBanner(TEST_LABEL_SHORTER_ATTRIBUTE_NAMES_NO_NULL);
CustomerShortNamesNoNull[] shorterOnesNoNull = CustomerShortNamesNoNull.fromCustomers(customers);
byte[] shorterJson = createPlainJson(TEST_LABEL_SHORTER_ATTRIBUTE_NAMES_NO_NULL, shorterOnesNoNull);
compressJson(TEST_LABEL_SHORTER_ATTRIBUTE_NAMES_NO_NULL, shorterJson);
}
@Test
void testSlim() throws IOException {
printBanner(TEST_LABEL_SLIM_CUSTOMER);
CustomerSlim[] slimOnes = CustomerSlim.fromCustomers(customers);
byte[] slimJson = createPlainJson(TEST_LABEL_SLIM_CUSTOMER, slimOnes);
compressJson(TEST_LABEL_SLIM_CUSTOMER, slimJson);
}
@Test
void testCustomSerializer() throws IOException {
printBanner(TEST_LABEL_CUSTOM_SERIALIZER);
SimpleModule serializer = new SimpleModule("CustomCustomerSerializer", new Version(1, 0, 0, null, null, null));
serializer.addSerializer(Customer.class, new CustomerSerializer());
mapper.registerModule(serializer);
SimpleModule deserializer = new SimpleModule("CustomCustomerDeserializer", new Version(1, 0, 0, null, null, null));
deserializer.addDeserializer(Customer.class, new CustomerDeserializer());
mapper.registerModule(deserializer);
byte[] plainJson = createPlainJson(TEST_LABEL_CUSTOM_SERIALIZER, customers);
compressJson(TEST_LABEL_CUSTOM_SERIALIZER, plainJson);
}
private void printBanner(String name) {
System.out.println();
System.out.println("************************************************");
System.out.println("Testing " + name);
System.out.println();
}
void compressJson(String label, byte[] plainJson) throws IOException {
ByteArrayOutputStream outpuStream = new ByteArrayOutputStream();
GZIPOutputStream gzipStream = new GZIPOutputStream(outpuStream);
gzipStream.write(plainJson);
gzipStream.close();
byte[] gzippedJson = outpuStream.toByteArray();
System.out.println(label + " GZIPped length: " + LENGTH_FORMATTER.format(gzippedJson.length));
assertTrue(plainJson.length > gzippedJson.length, label + " should be longer than GZIPped data");
}
private byte[] createPlainJson(String label, Object[] customers) throws IOException {
System.out.println(label + " sample: ");
ObjectWriter prettyWritter = mapper.writerWithDefaultPrettyPrinter();
System.out.println(prettyWritter.writeValueAsString(customers[0]));
byte[] feedback = mapper.writeValueAsBytes(customers);
System.out.println(label + " length: " + LENGTH_FORMATTER.format(feedback.length));
assertTrue(feedback.length > 1, label + " should be there");
String prefix = label.replaceAll(" ", "-")
.toLowerCase();
File tempFile = File.createTempFile("jon-optimization-" + prefix, ".json");
FileOutputStream fos = new FileOutputStream(tempFile);
fos.write(feedback);
fos.close();
System.out.println(label + " file: " + tempFile.toString());
Object[] restoredOnes = mapper.readValue(feedback, customers.getClass());
assertArrayEquals(TEST_LABEL_DEFAULT_JSON + ": restoring from JSON should work", customers, restoredOnes);
return feedback;
}
}

File diff suppressed because it is too large Load Diff