Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
223cbcf
initial draft
mudkipdev Apr 8, 2025
bb99460
add more tests
mudkipdev Apr 8, 2025
bcd707b
make copy methods public for now
mudkipdev Apr 8, 2025
8ed8242
make recursion depth test
mudkipdev Apr 8, 2025
3c6ccd6
fix tests
mudkipdev Apr 8, 2025
6b84b22
fix version in readme
mudkipdev Apr 8, 2025
ef95a91
fix pretty print
mudkipdev Apr 8, 2025
dd1fa2c
Update junit5 monorepo to v5.12.2
renovate[bot] Apr 11, 2025
07f4e24
Merge pull request #12 from mudkipdev/renovate/junit5-monorepo
mudkipdev Apr 11, 2025
31cc486
Update junit5 monorepo
renovate[bot] Apr 11, 2025
a1a4aa7
Merge pull request #13 from mudkipdev/renovate/junit5-monorepo
mudkipdev Apr 11, 2025
c2ecb3e
add external test suite
mudkipdev Apr 18, 2025
58d21d8
add json codec
mudkipdev Apr 18, 2025
22205e7
add built-in json codecs
mudkipdev Apr 20, 2025
4a5ced5
add file shorthands
mudkipdev Apr 23, 2025
8f102a4
thanks nopox
mudkipdev Apr 24, 2025
bb6a35d
Update dependency gradle to v8.14
renovate[bot] Apr 25, 2025
698b9e0
Merge pull request #14 from mudkipdev/renovate/gradle-8.x
mudkipdev Apr 25, 2025
5a590f2
add more json javadocs
mudkipdev Apr 29, 2025
be4f3e7
Update dependency io.netty:netty-buffer to v4.2.1.Final
renovate[bot] May 6, 2025
e871e74
Merge pull request #15 from mudkipdev/renovate/netty-monorepo
mudkipdev May 6, 2025
643c111
Update plugin com.vanniktech.maven.publish to v0.32.0
renovate[bot] May 14, 2025
b1a3705
Merge pull request #16 from mudkipdev/renovate/com.vanniktech.maven.p…
mudkipdev May 14, 2025
16d503d
Update dependency gradle to v8.14.1
renovate[bot] May 22, 2025
7b699e9
Merge pull request #17 from mudkipdev/renovate/gradle-8.x
mudkipdev May 23, 2025
1cdc478
Update junit5 monorepo
renovate[bot] May 30, 2025
9fe9ade
Merge pull request #18 from mudkipdev/renovate/junit5-monorepo
mudkipdev May 31, 2025
5f9df06
Update dependency io.netty:netty-buffer to v4.2.2.Final
renovate[bot] Jun 5, 2025
6909060
Merge pull request #19 from mudkipdev/renovate/netty-monorepo
mudkipdev Jun 5, 2025
80fa693
Update dependency gradle to v8.14.2
renovate[bot] Jun 5, 2025
14dbf0e
Merge pull request #20 from mudkipdev/renovate/gradle-8.x
mudkipdev Jun 6, 2025
4c8149d
Update junit5 monorepo
renovate[bot] Jun 7, 2025
397952a
Merge pull request #21 from mudkipdev/renovate/junit5-monorepo
mudkipdev Jun 7, 2025
e745e4c
add decode/encode only codecs
mudkipdev Jun 12, 2025
261b286
Merge remote-tracking branch 'origin/main' into json
mudkipdev Jun 17, 2025
c5ba2c4
temp benchmark code
mudkipdev Jun 17, 2025
5219dcd
update to java 25, update gradle, add write benchmark, add jackson an…
mudkipdev Feb 19, 2026
9b0e355
pick random json strings for benchmark
mudkipdev Feb 19, 2026
c14c1a7
add warning to readme
mudkipdev Feb 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
114 changes: 48 additions & 66 deletions .github/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# alpine
A binary<sup>(JSON soon™)</sup> serialization library for Java.
A binary and JSON serialization library for Java.

![](https://wakatime.com/badge/github/mudkipdev/alpine.svg)

## Installation
> [!WARNING]
> I'm in the process of setting up the Maven publishing again. JSON is currently not published, and for binary you should use the `0.1.1` version instead of `0.2.0`. This notice is temporary.

### Binary

<details>
Expand All @@ -12,8 +15,8 @@ A binary<sup>(JSON soon™)</sup> serialization library for Java.

```kts
dependencies {
implementation("dev.mudkip:alpine-binary:0.1.1")
implementation("io.netty:netty-buffer:4.2.0.Final")
implementation("dev.mudkip:alpine-binary:0.2.0")
implementation("io.netty:netty-buffer:4.2.10.Final")
}
```

Expand All @@ -25,8 +28,8 @@ dependencies {

```groovy
dependencies {
implementation 'dev.mudkip:alpine-binary:0.1.1'
implementation 'io.netty:netty-buffer:4.2.0.Final'
implementation 'dev.mudkip:alpine-binary:0.2.0'
implementation 'io.netty:netty-buffer:4.2.10.Final'
}
```

Expand All @@ -40,79 +43,58 @@ dependencies {
<dependency>
<groupId>dev.mudkip</groupId>
<artifactId>alpine-binary</artifactId>
<version>0.1.1</version>
<version>0.2.0</version>
</dependency>

<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
<version>4.2.0.Final</version>
<version>4.2.10.Final</version>
</dependency>
```

</details>

## Documentation
The core primitive of Alpine is a codec. A codec is something that can encode and decode an object from a byte buffer.
Netty's `ByteBuf` is used for this, however you don't need any other parts of Netty to take advantage of this system.

You can easily create an `Integer` codec like this:
```java
public static final BinaryCodec<Integer> INTEGER = new BinaryCodec<>() {
@Override
public Integer read(ByteBuf buffer) {
return buffer.readInt();
}

@Override
public void write(ByteBuf buffer, Integer value) {
buffer.writeInt(value);
}
};
### JSON

<details>
<summary>Gradle (Kotlin)</summary>
<br>

```kts
dependencies {
implementation("dev.mudkip:alpine-json:0.2.0")
}
```

### Built-in codecs
There are already many built-in codecs exposed through the `BinaryCodec` class, a partial list is available below:

| Java Type | Codec | Notes |
|-------------|-------------------|-------------------------------------------------------------------------------------|
| `boolean` | `BOOLEAN` | Encoded as `0` or `1`. |
| `byte` | `BYTE` | |
| `char` | `CHARACTER` | Encoded as a two-byte UTF-16 character. |
| `short` | `SHORT` | |
| `int` | `INTEGER` | |
| `int` | `VARINT` | [LEB128](https://en.wikipedia.org/wiki/LEB128) encoded. Uses between 1 and 5 bytes. |
| `long` | `LONG` | |
| `float` | `FLOAT` | |
| `double` | `DOUBLE` | |
| `String` | `STRING` | Encoded as UTF-8. Length-prefixed with a varint. |
| `UUID` | `UUID` | Encoded as two 64-bit integers. |

### Templates
Complex composite types can be created using the template syntax:

```java
public record User(String name, Gender gender, int age) {
public static final BinaryCodec<User> CODEC = BinaryTemplate.of(
STRING, User::name,
Gender.CODEC, User::gender,
INTEGER, User::age,
// include up to 20 fields (total)
User::new);
</details>

<details>
<summary>Gradle (Groovy)</summary>
<br>

```groovy
dependencies {
implementation 'dev.mudkip:alpine-json:0.2.0'
}
```

### Transformations
Use these methods to map a codec to another type.
- `.array()` → `T[]`
- `.list()` → `List<T>`
- `.nullable()` → `@Nullable T`
- `.optional()` → `Optional<T>`
- `.map(Function<T, U>, Function<U, T>)` → `U`

### There's more!
- Use `BinaryCodec.ordinal(Example.class)` to represent an enum.
- Use `BinaryCodec.unit(Example::new)` to represent singleton types.
- Use `BinaryCodec.map(keyCodec, valueCodec)` to represent a hash map.
- Use `BinaryCodec.either(leftCodec, rightCodec)` to represent something which can be one of two types.
- Use `BinaryCodec.of(Function<ByteBuf, T>, BiConsumer<ByteBuf, T>)` for an easier way to create a codec, especially if using the `::` syntax.
</details>

<details>
<summary>Maven</summary>
<br>

```xml
<dependency>
<groupId>dev.mudkip</groupId>
<artifactId>alpine-json</artifactId>
<version>0.2.0</version>
</dependency>
```

</details>

## Documentation
- [Binary](./binary.md)
- [JSON](./json.md)
64 changes: 64 additions & 0 deletions .github/binary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Documentation
The core primitive of Alpine is a codec. A codec is something that can encode and decode an object from a byte buffer.
Netty's `ByteBuf` is used for this, however you don't need any other parts of Netty to take advantage of this system.

You can easily create an `Integer` codec like this:
```java
public static final BinaryCodec<Integer> INTEGER = new BinaryCodec<>() {
@Override
public Integer read(ByteBuf buffer) {
return buffer.readInt();
}

@Override
public void write(ByteBuf buffer, Integer value) {
buffer.writeInt(value);
}
};
```

### Built-in codecs
There are already many built-in codecs exposed through the `BinaryCodec` class, a partial list is available below:

| Java Type | Codec | Notes |
|-------------|-------------------|-------------------------------------------------------------------------------------|
| `boolean` | `BOOLEAN` | Encoded as `0` or `1`. |
| `byte` | `BYTE` | |
| `char` | `CHARACTER` | Encoded as a two-byte UTF-16 character. |
| `short` | `SHORT` | |
| `int` | `INTEGER` | |
| `int` | `VARINT` | [LEB128](https://en.wikipedia.org/wiki/LEB128) encoded. Uses between 1 and 5 bytes. |
| `long` | `LONG` | |
| `float` | `FLOAT` | |
| `double` | `DOUBLE` | |
| `String` | `STRING` | Encoded as UTF-8. Length-prefixed with a varint. |
| `UUID` | `UUID` | Encoded as two 64-bit integers. |

### Templates
Complex composite types can be created using the template syntax:

```java
public record User(String name, Gender gender, int age) {
public static final BinaryCodec<User> CODEC = BinaryTemplate.of(
STRING, User::name,
Gender.CODEC, User::gender,
INTEGER, User::age,
// include up to 20 fields (total)
User::new);
}
```

### Transformations
Use these methods to map a codec to another type.
- `.array()` → `T[]`
- `.list()` → `List<T>`
- `.nullable()` → `@Nullable T`
- `.optional()` → `Optional<T>`
- `.map(Function<T, U>, Function<U, T>)` → `U`

### There's more!
- Use `BinaryCodec.ordinal(Example.class)` to represent an enum.
- Use `BinaryCodec.unit(Example::new)` to represent singleton types.
- Use `BinaryCodec.map(keyCodec, valueCodec)` to represent a hash map.
- Use `BinaryCodec.either(leftCodec, rightCodec)` to represent something which can be one of two types.
- Use `BinaryCodec.of(Function<ByteBuf, T>, BiConsumer<ByteBuf, T>)` for an easier way to create a codec, especially if using the `::` syntax.
2 changes: 2 additions & 0 deletions .github/json.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Documentation
This is not finished yet. [Take a look at the source code?](../json/src/main/java/alpine/json)
10 changes: 0 additions & 10 deletions .github/renovate.json

This file was deleted.

3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ jobs:
with:
name: alpine
path: |
binary/build/libs/alpine-*.*.*.jar
binary/build/libs/binary-*.*.*.jar
json/build/libs/json-*.*.*.jar
18 changes: 18 additions & 0 deletions benchmark/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins {
id("me.champeau.jmh") version "0.7.3"
}

dependencies {
jmhImplementation(project(":json"))
jmhImplementation("org.openjdk.jmh:jmh-core:1.37")
jmhImplementation("org.openjdk.jmh:jmh-generator-annprocess:1.37")
jmhImplementation("com.google.code.gson:gson:2.13.2")
jmhImplementation("tools.jackson.core:jackson-databind:3.0.4")
jmhImplementation("com.alibaba.fastjson2:fastjson2:2.0.60")
}

jmh {
warmupIterations.set(5)
iterations.set(5)
fork.set(1)
}
70 changes: 70 additions & 0 deletions benchmark/src/jmh/java/alpine/json/JsonReadBenchmark.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package alpine.json;

import com.alibaba.fastjson2.JSON;
import com.google.gson.JsonParser;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import tools.jackson.databind.ObjectMapper;

import java.util.concurrent.TimeUnit;

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
public class JsonReadBenchmark {
private static final ObjectMapper JACKSON = new ObjectMapper();

// Pool of 8 varied inputs (power of 2 for cheap index masking).
// Different string lengths, number values, array sizes and nesting depth
// prevent the branch predictor from memorizing a single character sequence.
private static final String[] INPUTS = {
"""
{"name":"Mudkip","type":"Water","stats":{"hp":50,"attack":70,"defense":50,"speed":40,"abilities":["Torrent","Damp"]},"evolution":{"level":16,"next":"Marshtomp"},"moves":[{"name":"Water Gun","power":40},{"name":"Tackle","power":35},{"name":"Mud-Slap","power":20}],"metadata":{"origin":"Hoenn","isLegendary":false}}
""",
"""
{"name":"Charizard","type":"Fire","stats":{"hp":78,"attack":84,"defense":78,"speed":100,"abilities":["Blaze","Solar Power"]},"evolution":{"level":36,"next":null},"moves":[{"name":"Flamethrower","power":90},{"name":"Dragon Claw","power":80},{"name":"Air Slash","power":75},{"name":"Fire Blast","power":110}],"metadata":{"origin":"Kanto","isLegendary":false}}
""",
"""
{"name":"Pikachu","type":"Electric","stats":{"hp":35,"attack":55,"defense":40,"speed":90,"abilities":["Static","Lightning Rod"]},"evolution":{"level":null,"next":"Raichu"},"moves":[{"name":"Thunderbolt","power":90},{"name":"Quick Attack","power":40}],"metadata":{"origin":"Kanto","isLegendary":false}}
""",
"""
{"name":"Mewtwo","type":"Psychic","stats":{"hp":106,"attack":110,"defense":90,"speed":130,"abilities":["Pressure","Unnerve"]},"evolution":{"level":null,"next":null},"moves":[{"name":"Psystrike","power":100},{"name":"Shadow Ball","power":80},{"name":"Aura Sphere","power":80},{"name":"Ice Beam","power":90},{"name":"Recover","power":0}],"metadata":{"origin":"Kanto","isLegendary":true}}
""",
"""
{"name":"Bulbasaur","type":"Grass","stats":{"hp":45,"attack":49,"defense":49,"speed":45,"abilities":["Overgrow","Chlorophyll"]},"evolution":{"level":16,"next":"Ivysaur"},"moves":[{"name":"Vine Whip","power":45},{"name":"Razor Leaf","power":55},{"name":"Tackle","power":35}],"metadata":{"origin":"Kanto","isLegendary":false}}
""",
"""
{"name":"Snorlax","type":"Normal","stats":{"hp":160,"attack":110,"defense":65,"speed":30,"abilities":["Immunity","Thick Fat","Gluttony"]},"evolution":{"level":null,"next":null},"moves":[{"name":"Body Slam","power":85},{"name":"Crunch","power":80},{"name":"Rest","power":0},{"name":"Snore","power":50}],"metadata":{"origin":"Kanto","isLegendary":false}}
""",
"""
{"name":"Gengar","type":"Ghost","stats":{"hp":60,"attack":65,"defense":60,"speed":110,"abilities":["Cursed Body"]},"evolution":{"level":null,"next":"Mega Gengar"},"moves":[{"name":"Shadow Ball","power":80},{"name":"Sludge Bomb","power":90},{"name":"Dazzling Gleam","power":80},{"name":"Thunderbolt","power":90}],"metadata":{"origin":"Kanto","isLegendary":false}}
""",
"""
{"name":"Eevee","type":"Normal","stats":{"hp":55,"attack":55,"defense":50,"speed":55,"abilities":["Run Away","Adaptability","Anticipation"]},"evolution":{"level":null,"next":null},"moves":[{"name":"Swift","power":60},{"name":"Bite","power":60},{"name":"Covet","power":60}],"metadata":{"origin":"Kanto","isLegendary":false}}
"""
};

private int index = 0;

@Benchmark
public void alpine(Blackhole blackhole) throws Exception {
blackhole.consume(Json.read(INPUTS[this.index++ % INPUTS.length]));
}

@Benchmark
public void gson(Blackhole blackhole) throws Exception {
blackhole.consume(JsonParser.parseString(INPUTS[this.index++ % INPUTS.length]));
}

@Benchmark
public void jackson(Blackhole blackhole) throws Exception {
blackhole.consume(JACKSON.readTree(INPUTS[this.index++ % INPUTS.length]));
}

@Benchmark
public void fastjson(Blackhole blackhole) throws Exception {
blackhole.consume(JSON.parseObject(INPUTS[this.index++ % INPUTS.length]));
}
}
Loading