Web Analytics

Hello world, mapneat!

mapneat is a library I’ve written a long time ago. I am biased.

Introduction

One of the most common challenges I’ve encountered in Software Engineering is data manipulation: taking a raw data structure, applying business logic, restructuring it, and finally serializing it into a JSON format for external consumption.

mapneat is designed for exactly this purpose. It provides a declarative DSL for transforming JSON, XML, and POJOs into JSON with minimal boilerplate. While written in Kotlin, it is fully compatible with Java projects.

The Tech Stack

Under the hood, mapneat leverages several proven libraries:

A Simple Example

Consider the following domain model for a travel-focused social application:

class User(
    val id: Long,
    val firstName: String,
    val lastName: String,
    val birthDate: String,
    val friends: MutableList<Long>,
    val visits: Set<Visit>,
    val creditCards: Set<CreditCardInfo>,
    val pwd: String,
    val email: String,
    val userName: String
)

class Visit(val id: Long, val country: String, val enter: String)
class CreditCardInfo(val number: String, val cvv: String, val expirationDate: String)

If we serialized a User object directly, the output would include sensitive data like passwords and financial info—not ideal for a public REST API.

The Transformation Goal

Instead of the raw dump, we want a “morphed” version that:

  1. Redacts pwd and creditCards.
  2. Flattens visits to just a list of unique country names.
  3. Normalizes the lastName to uppercase.
  4. Resolves friend IDs into their actual full names.

Here is how you express that transformation using the mapneat DSL:

val users : Map<Long, User> = getUsers(100)
val aRandomUser = users[10]

val out = json(fromObject(aRandomUser)) {
    "" *= "$"             // Start by shifting everything from source to target
    - "visits"            // Delete specific nodes
    - "creditCards"
    - "pwd"
    
    "visited" *= {        // Extract and process countries
        expression = "$.visits[*].country"
        processor = {
            (it as Iterable<String>).toSet()
        }
    }
    
    // Modify existing target data
    "lastName" /= { targetCtx().read<String>("$.lastName").toUpperCase() }
    
    // Resolve IDs to Names
    "friends" /= {
        targetCtx()
            .read<List<Long>>("$.friends")
            .map { id -> "${users[id]?.firstName} ${users[id]?.lastName}" }
    }
}
println(out)

The Result

The “morphed” JSON output is clean, safe, and ready for the client:

{
  "id" : 490,
  "firstName" : "Rhea",
  "lastName" : "ORLICH",
  "birthDate" : "1925-07-09",
  "friends" : [ "Bunny Winstanley", "Dianna Imaizumi", "Verdell Aguillar" ],
  "email" : "crinedbotfly@msn.com",
  "userName" : "fainleif",
  "visited" : [ "Saint Lucia", "Falkland Islands (malvinas)", "Malawi", "Kenya", "Lesotho" ]
}

Understanding the Operations

A mapneat transformation always wraps a series of operations within a json() block.

1. The Shift Operation (*=)

This is the core of the library. It uses JsonPath to move data from the source to the target.

2. The Delete Operation (-)

The - operator removes a node and all of its descendants from the target. It is used here to scrub sensitive fields.

3. The Assign Operation (/=)

Unlike the shift operation which pulls from the source, the assign operation is typically used to set a value in the target based on logic, often reading from the current targetCtx(). In our example, we used it to uppercase the name and map IDs to names.

Moving Forward

This example only scratches the surface. mapneat supports complex nested mapping, XML sources, and more.

For deep-dives into every supported operation, check out the official documentation or explore the example repository.





Source Code & Contributions

Spot an error or have an improvement? Open a PR directly for this article .



<< Previous Post

|

Next Post >>