Skip to content
(un)wrapping extensions for Gson.
Branch: master
Clone or download
Latest commit 7490347 May 24, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.circleci Prepare for release 1.0.0 May 23, 2019
gradle/wrapper Add boilerplate May 20, 2019
src Upload artifacts on post-step May 23, 2019
.gitignore
CHANGELOG.md Prepare for release 1.0.0 May 23, 2019
LICENSE
README.md Update README.md May 24, 2019
build.gradle
gradle.properties Add boilerplate May 20, 2019
gradlew Add boilerplate May 20, 2019
gradlew.bat Add boilerplate May 20, 2019
settings.gradle

README.md

Sweep Gson

CircleCI codecov Download

Sweep Gson adds (un)wrapping functionality to Gson without modifying any existing behavior.

Download

Gradle:

dependencies {
  implementation 'io.saeid.sweep:sweep-gson:1.0.0'
}

Usage

GsonBuilder().withSweep().create()

If you need more advance features:

GsonBuilder().withSweep {
      defaultWrapper = ...    // optional
      defaultUnwrapper = ...  // optional
      hooks = ...             // optional
}.create()

SweepWrapper

Use @SweepWrapper annotation to wrap the object with your desired value during serialization.

@SweepWrapper("request")
data class Request(val name : String)

The output after serializing the above class:

{
  "request" : {
    "name": "your_value"
  }
}

Nested Wrapping

@SweepWrapper also supports nested wrapping using dot as delimiter:

For instance, If you replace the value in the above example to @SweepWrapper("request.data"), It will generate:

{
  "request": {
    "data": {
      "name": "your_value"
    }
  }
}

Custom/Default Wrapping

If you want to use the class name as the wrapper value you can simply use @SweepWrapper(USE_CLASS_NAME_WRAPPER).

USE_CLASS_NAME_WRAPPER is a reserved word which will force @SweepWrapper to use the class name (decapitalized version) as the wrapper name.

For instance:

@SweepWrapper(USE_CLASS_NAME_WRAPPER)
data class Request(val name : String)
{
  "request" : {
    "name": "your_value"
  }
}

Also you can define the @SweepWrapper value at runtime by overriding defaultWrapper.

GsonBuilder().withSweep {
      defaultWrapper = object : DefaultWrapper {
         override fun <T> wrapWith(value: T): String? {
            return "request.$USE_CLASS_NAME_WRAPPER"
         }
      }
}.create()

Note: By default @SweepWrapper will switch to the defaultWrapper, If you don't pass any value.

SweepUnwrapper

Use @SweepUnwrapper annotation to unwrap the object with your desired value during deserialization. Unlike @SweepWrapper, @SweepUnwrapper only works on the root object.

{
  "response" : {
    "name": "your_value"
  }
}

For instance, The above JSON can be deserialized to the class below:

@SweepWrapper("response")
data class Response(val name : String)

Nested Unwrapping

@SweepUnwrapper also supports nested unwrapping using dot as delimiter:

For instance, If you replace the value in the above example to @SweepUnwrapper("response.body"), It can be extracted by the JSON below:

{
  "response": {
    "body": {
      "name": "your_value"
    }
  }
}

Custom/Default Unwrapping

Like @SweepWrapper, It supports USE_CLASS_NAME_UNWRAPPER.

Also you can define the @SweepUnwrapper value at runtime by overriding defaultUnwrapper.

GsonBuilder().withSweep {
      defaultUnwrapper = object : DefaultUnwrapper {
        override fun <T> unwrapWith(type: Class<T>): String? = null
      }
      
      override fun force() : Boolean = true

}.create()

@SweepUnwrapper also supports force-mode, which means It will unwrapp all objects event If they're not annotated with @SweepUnwrapper during deserialization.

If you want to disable force mode for a specific type, you can easily pass null.

Note: By default @SweepUnwrapper will switch to the defaultUnwrapper, If you don't pass any value.

startsWith/endsWith

@SweepUnwrapper also supports a simple starts/ends-With regex.

  • @SweepUnwrapper("*Response") It will unwrap everything ends with Response, e.g. singleResponse
  • @SweepUnwrapper("response*") It will unwrap everything starts with response, e.g. responseValue

Hooks

Sweep Gson allows to add an object to the root element before serialization by overriding addToRoot method from hooks:

GsonBuilder().withSweep {
      
      hooks = object : Hooks {
          override fun <T> addToRoot(value: T): Pair<String, Any>? {
             return Pair("properties", Properties(...)
          }
      }
}.create()

It will adds properties to the root classes annotated with SweepWrapper.

{
   "properties" : { 
       ... 
   }
   ...
}

Sample

Assume that you have an REST API with the request/response template below:

// request
{
    "properties": {
        "device": "user's device"
    },
    "request": {
        "request_type": {
	  }
    }
}

// response
{
    "response": {
         "response_typeReply": { 
         }
    }     
}

First create our DTOs:

@SweepWrapper
data class Login(val userName: String, val password: String)

@SweepUnwrapper
data class User(val name: String)

Then we create our Gson instance using withSweep:

    GsonBuilder().withSweep {
        // tell sweep gson to unwrap every object that match `response.*Reply`
        defaultUnwrapper = object : DefaultUnwrapper {
            override fun <T> unwrapWith(type: Class<T>): String? = "response.*Reply"
            override fun force(): Boolean = true
        }
        // tell sweep gson to wrap every annotated object with `request.[the class name of that object]`        
        defaultWrapper = object : DefaultWrapper {
            override fun <T> wrapWith(value: T): String = "request.$USE_CLASS_NAME_WRAPPER"
        }
        // add Properties to the root of our objects during serialization
        hooks = object : Hooks {
            override fun <T> addToRoot(value: T): Pair<String, Any>? {
                return Pair("properties", Properties("Android"))
            }
        }
    }.create()

And now the result:

gson.toJson(Login("admin", "admin")) 
// prints 
// {"properties":{"device":"Android"},"request":{"login":{"userName":"admin","password":"admin"}}}

gson.fromJson<User>("""{ "response": { "userReply": { "name":"admin" } } }""", User::class.java)
// prints
// User(name=admin)

Limitations

  • Unwrapper only unwraps from the root element.

For example, you can not deserialize the below JSON

{
  "parent": {
    "root" : {
      "name" : "sweep"
    }
  }
}

to

data class Root(val parent : Parent)

data class Parent(val child : Child)

@SweepUnwrapper("root")
data class Child(val name : String)
  • Unwrapper will ignore sibling elements while deserializing.

For example, version will be null after deserialization, but child will be deserialized.

{
  "parent": {
    "root" : {
      "name" : "sweep"
    }
  }
}
@SweepUnwrapper("root")
data class Root(val version : String, val child : Child)

data class Child(val name : String)
  • addToRoot only works If the root class is annotated with SweepWrapper.
  • Unlike SweepUnwrapper, there is no force mode available for SweepWrapper.
You can’t perform that action at this time.