MoshiPack icon indicating copy to clipboard operation
MoshiPack copied to clipboard

MessagePack implementation built on Moshi & OKIO -- msgpack.org[Kotlin]

MoshiPack

CircleCI

Gradle

implementation com.daveanthonythomas.moshipack:moshipack:1.0.1

Optional Retrofit support:

implementation com.daveanthonythomas.moshipack:moshipack-retrofit:1.0.1

About

This is a Kotilin implementation of MessagePack serialization and deserialization built ontop of Moshi to take advantage of Moshi's type adapters and utilizes okio for reading and writing MessagePack bytes.

The library is intended to be consumed in a Kotlin project, and is not intended for Java use.

Inspired by Kaushik Gopal's tweet

See Moshi for adapter usage and reference.

Convert an object to MessagePack format

data class MessagePackWebsitePlug(var compact: Boolean = true, var schema: Int = 0)

val moshiPack = MoshiPack()
val packed: BufferedSource = moshiPack.pack(MessagePackWebsitePlug())

println(packed.readByteString().hex())

This prints the MessagePack bytes as a hex string 82a7636f6d70616374c3a6736368656d6100

  • 82 - Map with two entries
  • a7 - String of seven bytes
  • 63 6f 6d 70 61 63 74 - UTF8 String "compact"
  • c3 - Boolean value true
  • a6 - String of size bytes
  • 73 63 68 65 6d 61 - UTF8 String "schema"
  • 00 - Integer value 0

Convert binary MessagePack back to an Object

val bytes = ByteString.decodeHex("82a7636f6d70616374c3a6736368656d6100").toByteArray()

val moshiPack = MoshiPack()
val plug: MessagePackWebsitePlug = moshiPack.unpack(bytes)

Static API

If you prefer to not instantiate a MoshiPack instance you can access the API in a static fashion as well. Note this will create a new Moshi instance every time you make an API call. You may want to use the API this way if you aren't providing MoshiPack by some form of dependency injection and you do not have any specific builder parameters for Moshi


Format Support

See MessagePack format spec for further reference.

format namefirst byte (in binary)first byte (in hex)Supported
positive fixint0xxxxxxx0x00 - 0x7fYes
fixmap1000xxxx0x80 - 0x8fYes
fixarray1001xxxx0x90 - 0x9fYes
fixstr101xxxxx0xa0 - 0xbfYes
nil110000000xc0Yes
(never used)110000010xc1Yes
false110000100xc2Yes
true110000110xc3Yes
bin 8110001000xc4No
bin 16110001010xc5No
bin 32110001100xc6No
ext 8110001110xc7No
ext 16110010000xc8No
ext 32110010010xc9No
float 32110010100xcaYes
float 64110010110xcbYes
uint 8110011000xccYes
uint 16110011010xcdYes
uint 32110011100xceYes
uint 64110011110xcfYes
int 8110100000xd0Yes
int 16110100010xd1Yes
int 32110100100xd2Yes
int 64110100110xd3Yes
fixext 1110101000xd4No
fixext 2110101010xd5No
fixext 4110101100xd6No
fixext 8110101110xd7No
fixext 16110110000xd8No
str 8110110010xd9Yes
str 16110110100xdaYes
str 32110110110xdbYes
array 16110111000xdcYes
array 32110111010xddYes
map 16110111100xdeYes
map 32110111110xdfYes
negative fixint111xxxxx0xe0 - 0xffYes

API

pack

Serializes an object into MessagePack. Returns: okio.BufferedSource

Instance version:

MoshiPack().pack(anyObject)

Static version:

MoshiPack.pack(anyObject)

packToByeArray

If you prefer to get a ByteArray instead of a BufferedSource you can use this method.

Instance version only

MoshiPack().packToByteArray(anObject)

Static can be done

MoshiPack.pack(anObject).readByteArray()

unpack

Deserializes MessagePack bytes into an Object. Returns: T: Any Works with ByteArray and okio.BufferedSource

Instance version:

// T must be valid type so Moshi knows what to deserialize to
val unpacked: T = MoshiPack().unpack(byteArray)

Static version:

val unpacked: T = MoshiPack.upack(byteArray)

Instance version:

val unpacked: T = MoshiPack().unpack(bufferedSource)

Static version:

val unpacked: T = MoshiPack.upack(bufferedSource)

T can be an Object, a List, a Map, and can include generics. Unlike Moshi you do not need to specify a parameterized type to deserialize to a List with generics. MoshiPack can infer the paramterized type for you.

The following examples are valid for MoshiPack:

A typed List

val listCars: List<Car> = MoshiPack.unpack(carMsgPkBytes)

A List of Any

val listCars: List<Any> = MoshiPack.unpack(carMsgPkBytes)

An Object

val car: Car = MoshiPack.unpack(carBytes)

A Map of Any, Any

val car: Map<Any, Any> = MoshiPack.unpack(carBytes)

msgpackToJson

Convert directly from MessagePack bytes to JSON. Use this method for the most effecient implementation as no objects are instantiated in the process. This uses the FormatInterchange class to match implementations of JsonReader and a JsonWriter. If you wanted to say support XML as a direct conversion to and from, you could implement Moshi's JsonReader and JsonWriter classes and use the FormatInterchange class to convert directly to other formats. Returns String containing a JSON representation of the MessagePack data

Instance versions: (takes ByteArray or BufferedSource)

MoshiPack().msgpackToJson(byteArray)
MoshiPack().msgpackToJson(bufferedSource)

Static versions: (takes ByteArray or BufferedSource)

MoshiPack.msgpackToJson(byteArray)
MoshiPack.msgpackToJson(bufferedSource)

jsonToMsgpack

Convert directly from JSON to MessagePack bytes. Use this method for the most effecient implementation as no objects are instantiated in the process. Returns BufferedSource

Instance versions: (takes String or BufferedSource)

MoshiPack().jsonToMsgpack(jsonString)
MoshiPack().jsonToMsgpack(bufferedSource)

Static versions: (takes String or BufferedSource)

MoshiPack.jsonToMsgpack(jsonString)
MoshiPack.jsonToMsgpack(bufferedSource)

MoshiPack - constructor + Moshi builder

The MoshiPack constructor takes an optional Moshi.Builder.() -> Unit lambda which is applied to the builder that is used to instantiate the Moshi instance it uses.

Example adding custom adapter:

val moshiPack = MoshiPack({
  add(customAdapter)
})

Moshi is also a settable property which can be changed on a MoshiPack instance:

val m = MoshiPack()
m.moshi = Moshi.Builder().build()

The static version of the API also can be passed a lambda to applied to the Moshi.Builder used to instantiate Moshi:

MoshiPack.pack(someBytes) { add(customAdapter) }

Forcing integers to write as certain format

  • new in v1.0.1

This will force all integers to be packed as the type given. By default the smallest message pack type is used for integers.

val moshiPack = MoshiPack().apply {
    writerOptions.writeAllIntsAs = MsgpackIntByte.INT_64
}

Kotiln Support

Since this library is intended for Kotlin use, the moshi-kotlin artifact is included as a depedency. A KotlinJsonAdapterFactory is added by default to the instantiated Moshi that MoshiPack uses. This adapter allows for the use of Moshi's annotaions in Kotlin. To learn more about it see the Moshi documentation.

If you'd like to use Moshi with out a KotlinJsonAdapterFactory supply a Moshi instance for MoshiPack:

MoshiPack(moshi = Moshi.Builder().build)

ProGuard

From Moshi's README.md; If you are using ProGuard you might need to add the following options:

-dontwarn okio.**
-dontwarn javax.annotation.**
-keepclasseswithmembers class * {
    @com.squareup.moshi.* <methods>;
}
-keep @com.squareup.moshi.JsonQualifier interface *
-keepclassmembers class kotlin.Metadata {
    public <methods>;
}

Retrofit

An example of using the retorfit adapter can be found here: https://github.com/davethomas11/MoshiPack_AndroidAppExample