sweep-gson
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 withResponse, e.g.singleResponse@SweepUnwrapper("response*")It will unwrap everything starts withresponse, 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)
addToRootonly works If the root class is annotated withSweepWrapper.- Unlike
SweepUnwrapper, there is no force mode available forSweepWrapper.
