Data Processing and Format Conversion Using Jackson

Overview and Core Architecture

Jackson is a high-performance data-binding framework for the Java Virtual Machine. While predominantly known for JSON manipulation, it delivers a consistent API for multiple data representations, including XML, CSV, and standard Java properties files. The architecture centers on the ObjectMapper class, which serves as the base for specialized mappers like XmlMapper and JavaPropsMapper. Serialization operations utilize methods prefixed with write, while deserialization leverages read variants.

Dependency Configuration

Integrate the necessary modules into your Maven project to enable format-specific processing:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example.jacksondemo</groupId>
    <artifactId>jackson-formats-demo</artifactId>
    <version>1.0.0</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <jackson.version>2.15.2</jackson.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-properties</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

JSON Menipulation

Serialization

Configure the mapper to skip empty values and tolerate unexpected JSON fields. The following implementation demonstrates converting a POJO, a collection, and a key-value map into JSON strings.

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;

public class JsonConverterTest {

    private static final ObjectMapper JSON_PROCESSOR = new ObjectMapper();

    static {
        JSON_PROCESSOR.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        JSON_PROCESSOR.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    @Test
    public void serializeStructures() throws JsonProcessingException {
        CustomerProfile profile = new CustomerProfile();
        profile.setIdentifier(42L);
        profile.setDisplayName("Alice Smith");
        String jsonProfile = JSON_PROCESSOR.writeValueAsString(profile);
        System.out.println("Object JSON: " + jsonProfile);

        List<CustomerProfile> profileGroup = new ArrayList<>();
        profileGroup.add(profile);
        String jsonList = JSON_PROCESSOR.writeValueAsString(profileGroup);
        System.out.println("List JSON: " + jsonList);

        Map<String, Object> metadata = new HashMap<>();
        metadata.put("region", "US-West");
        metadata.put("tier", "Premium");
        String jsonMap = JSON_PROCESSOR.writeValueAsString(metadata);
        System.out.println("Map JSON: " + jsonMap);
    }
}

class CustomerProfile {
    private Long identifier;
    private String displayName;

    public Long getIdentifier() { return identifier; }
    public void setIdentifier(Long identifier) { this.identifier = identifier; }
    public String getDisplayName() { return displayName; }
    public void setDisplayName(String displayName) { this.displayName = displayName; }

    @Override
    public String toString() {
        return "CustomerProfile{id=" + identifier + ", name='" + displayName + "'}";
    }
}

Deserialization

Parsing JSON requires specifying the target type. Generic collections demand explicit type hints via TypeReference or TypeFactory to preserve element types during conversion.

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.junit.Test;
import java.util.List;

public class JsonParserTest {

    private static final ObjectMapper JSON_PROCESSOR = new ObjectMapper();

    @Test
    public void parseStructures() throws JsonProcessingException {
        String singleObjectPayload = "{\"identifier\":42,\"displayName\":\"Alice Smith\"}";
        CustomerProfile parsedProfile = JSON_PROCESSOR.readValue(singleObjectPayload, CustomerProfile.class);
        System.out.println(parsedProfile);

        String arrayPayload = "[{\"identifier\":42,\"displayName\":\"Alice Smith\"}]";
        
        // Generic list without type hint yields LinkedHashMap elements
        List<?> rawList = JSON_PROCESSOR.readValue(arrayPayload, List.class);
        System.out.println("Raw List Element Type: " + rawList.get(0).getClass().getSimpleName());

        // Using TypeReference for precise generic mapping
        List<CustomerProfile> typedList = JSON_PROCESSOR.readValue(arrayPayload, new TypeReference<List<CustomerProfile>>() {});
        System.out.println("Typed List: " + typedList.get(0));

        // Using TypeFactory as an alternative approach
        JavaType collectionType = JSON_PROCESSOR.getTypeFactory().constructCollectionType(List.class, CustomerProfile.class);
        List<CustomerProfile> factoryList = JSON_PROCESSOR.readValue(arrayPayload, collectionType);
        System.out.println("Factory List: " + factoryList.get(0));
    }
}

XML Document Processing

Structure Customization and Serialization

The XmlMapper extends standard data-binding to support XML syntax. Annotations control root element names, wrapper collections, CDATA blocks, and property naming mismatches between Java and XML schemas.

import com.ctc.wstx.api.WstxOutputProperties;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.XmlFactory;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlCData;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
import org.junit.Test;
import javax.xml.stream.XMLOutputFactory;
import java.util.Arrays;
import java.util.List;

public class XmlSerializerTest {

    private static final XmlMapper XML_HANDLER = new XmlMapper();

    static {
        XmlFactory xmlFactory = XML_HANDLER.getFactory();
        XMLOutputFactory xmlOutputFactory = xmlFactory.getXMLOutputFactory();
        xmlOutputFactory.setProperty(WstxOutputProperties.P_USE_DOUBLE_QUOTES_IN_XML_DECL, true);
        XML_HANDLER.enable(ToXmlGenerator.Feature.WRITE_XML_1_1);
        XML_HANDLER.enable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION);
    }

    @Test
    public void generateXmlDocument() throws JsonProcessingException {
        PurchaseOrder order = buildSampleOrder();
        String formattedXml = XML_HANDLER.writerWithDefaultPrettyPrinter().writeValueAsString(order);
        System.out.println(formattedXml);
    }

    private PurchaseOrder buildSampleOrder() {
        PurchaseOrder order = new PurchaseOrder();
        order.setOrderNumber("ORD-9981");
        order.setGrandTotal(1500.00);
        order.setSpecialNotes("Handle with care <<Fragile>>");
        
        LineItem item1 = new LineItem("SKU-A1", 5, 100.00);
        LineItem item2 = new LineItem("SKU-B2", 10, 100.00);
        order.setItems(Arrays.asList(item1, item2));
        return order;
    }
}

@JacksonXmlRootElement(localName = "purchaseOrder")
class PurchaseOrder {
    private String orderNumber;
    
    @JacksonXmlProperty(localName = "totalAmount")
    private Double grandTotal;

    @JacksonXmlCData
    private String specialNotes;

    @JacksonXmlElementWrapper(localName = "lineItems")
    @JacksonXmlProperty(localName = "item")
    private List<LineItem> items;

    public String getOrderNumber() { return orderNumber; }
    public void setOrderNumber(String orderNumber) { this.orderNumber = orderNumber; }
    public Double getGrandTotal() { return grandTotal; }
    public void setGrandTotal(Double grandTotal) { this.grandTotal = grandTotal; }
    public String getSpecialNotes() { return specialNotes; }
    public void setSpecialNotes(String specialNotes) { this.specialNotes = specialNotes; }
    public List<LineItem> getItems() { return items; }
    public void setItems(List<LineItem> items) { this.items = items; }
}

class LineItem {
    private String sku;
    private int quantity;
    private Double unitPrice;

    public LineItem() {}
    public LineItem(String sku, int quantity, Double unitPrice) {
        this.sku = sku;
        this.quantity = quantity;
        this.unitPrice = unitPrice;
    }

    public String getSku() { return sku; }
    public void setSku(String sku) { this.sku = sku; }
    public int getQuantity() { return quantity; }
    public void setQuantity(int quantity) { this.quantity = quantity; }
    public Double getUnitPrice() { return unitPrice; }
    public void setUnitPrice(Double unitPrice) { this.unitPrice = unitPrice; }
}

XML Deserialization

Reading XML back into Java objects requires matching annotations to the source structure. Property-level mapping handles tag name discrepancies automatically.

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlCData;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import org.junit.Test;
import java.util.List;

public class XmlParserTest {

    private static final XmlMapper XML_HANDLER = new XmlMapper();

    @Test
    public void parseXmlDocument() throws JsonProcessingException {
        String sourceXml = "<?xml version='1.1' encoding='UTF-8'?>\n" +
                "<purchaseOrder>\n" +
                "  <orderNumber>ORD-9981</orderNumber>\n" +
                "  <totalAmount>1500.0</totalAmount>\n" +
                "  <specialNotes><![CDATA[Handle with care <<Fragile>>]]></specialNotes>\n" +
                "  <lineItems>\n" +
                "    <item><sku>SKU-A1</sku><quantity>5</quantity><unitPrice>100.0</unitPrice></item>\n" +
                "    <item><sku>SKU-B2</sku><quantity>10</quantity><unitPrice>100.0</unitPrice></item>\n" +
                "  </lineItems>\n" +
                "</purchaseOrder>";

        PurchaseOrder parsedOrder = XML_HANDLER.readValue(sourceXml, PurchaseOrder.class);
        System.out.println("Parsed Order: " + parsedOrder.getOrderNumber());
        System.out.println("Total: " + parsedOrder.getGrandTotal());
        System.out.println("Notes: " + parsedOrder.getSpecialNotes());
        for (LineItem line : parsedOrder.getItems()) {
            System.out.println("  Item: " + line.getSku() + " x " + line.getQuantity());
        }
    }
}

Java Properties Management

The JavaPropsMapper translates hierarchical object structures into flat .properties format and vice versa, making it suitable for configuration file handling.

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.javaprop.JavaPropsMapper;
import org.junit.Test;

public class PropertiesHandlerTest {

    private static final JavaPropsMapper PROPS_PROCESSOR = new JavaPropsMapper();

    @Test
    public void serializeProperties() throws JsonProcessingException {
        EndpointConfig config = new EndpointConfig();
        config.setApiToken("tkn_x8f9s2a");
        config.setBaseUrl("https://api.example.com/v2");
        config.setMaxRetries(3);
        
        String propsOutput = PROPS_PROCESSOR.writeValueAsString(config);
        System.out.println(propsOutput);
    }

    @Test
    public void deserializeProperties() throws JsonProcessingException {
        String propsInput = "apiToken=tkn_x8f9s2a\n" +
                "baseUrl=https://api.example.com/v2\n" +
                "maxRetries=3";
                
        EndpointConfig loadedConfig = PROPS_PROCESSOR.readValue(propsInput, EndpointConfig.class);
        System.out.println("Loaded URL: " + loadedConfig.getBaseUrl());
        System.out.println("Retry Count: " + loadedConfig.getMaxRetries());
    }
}

class EndpointConfig {
    private String apiToken;
    private String baseUrl;
    private int maxRetries;

    public String getApiToken() { return apiToken; }
    public void setApiToken(String apiToken) { this.apiToken = apiToken; }
    public String getBaseUrl() { return baseUrl; }
    public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }
    public int getMaxRetries() { return maxRetries; }
    public void setMaxRetries(int maxRetries) { this.maxRetries = maxRetries; }
}

Tags: jackson objectmapper json-serialization xml-data-binding java-properties

Posted on Mon, 11 May 2026 07:44:37 +0000 by Thauwa