Skip to content

Commit 2b124dd

Browse files
committed
add json support
1 parent d2852ed commit 2b124dd

362 files changed

Lines changed: 3477 additions & 141 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/README.md

Lines changed: 48 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# alpine
2-
A binary<sup>(JSON soon™)</sup> serialization library for Java.
2+
A binary and JSON serialization library for Java.
33

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

66
## Installation
7+
> [!WARNING]
8+
> 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.
9+
710
### Binary
811

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

1316
```kts
1417
dependencies {
15-
implementation("dev.mudkip:alpine-binary:0.1.1")
16-
implementation("io.netty:netty-buffer:4.2.0.Final")
18+
implementation("dev.mudkip:alpine-binary:0.2.0")
19+
implementation("io.netty:netty-buffer:4.2.10.Final")
1720
}
1821
```
1922

@@ -25,8 +28,8 @@ dependencies {
2528

2629
```groovy
2730
dependencies {
28-
implementation 'dev.mudkip:alpine-binary:0.1.1'
29-
implementation 'io.netty:netty-buffer:4.2.0.Final'
31+
implementation 'dev.mudkip:alpine-binary:0.2.0'
32+
implementation 'io.netty:netty-buffer:4.2.10.Final'
3033
}
3134
```
3235

@@ -40,79 +43,58 @@ dependencies {
4043
<dependency>
4144
<groupId>dev.mudkip</groupId>
4245
<artifactId>alpine-binary</artifactId>
43-
<version>0.1.1</version>
46+
<version>0.2.0</version>
4447
</dependency>
4548

4649
<dependency>
4750
<groupId>io.netty</groupId>
4851
<artifactId>netty-buffer</artifactId>
49-
<version>4.2.0.Final</version>
52+
<version>4.2.10.Final</version>
5053
</dependency>
5154
```
5255

5356
</details>
5457

55-
## Documentation
56-
The core primitive of Alpine is a codec. A codec is something that can encode and decode an object from a byte buffer.
57-
Netty's `ByteBuf` is used for this, however you don't need any other parts of Netty to take advantage of this system.
58-
59-
You can easily create an `Integer` codec like this:
60-
```java
61-
public static final BinaryCodec<Integer> INTEGER = new BinaryCodec<>() {
62-
@Override
63-
public Integer read(ByteBuf buffer) {
64-
return buffer.readInt();
65-
}
66-
67-
@Override
68-
public void write(ByteBuf buffer, Integer value) {
69-
buffer.writeInt(value);
70-
}
71-
};
58+
### JSON
59+
60+
<details>
61+
<summary>Gradle (Kotlin)</summary>
62+
<br>
63+
64+
```kts
65+
dependencies {
66+
implementation("dev.mudkip:alpine-json:0.2.0")
67+
}
7268
```
7369

74-
### Built-in codecs
75-
There are already many built-in codecs exposed through the `BinaryCodec` class, a partial list is available below:
76-
77-
| Java Type | Codec | Notes |
78-
|-------------|-------------------|-------------------------------------------------------------------------------------|
79-
| `boolean` | `BOOLEAN` | Encoded as `0` or `1`. |
80-
| `byte` | `BYTE` | |
81-
| `char` | `CHARACTER` | Encoded as a two-byte UTF-16 character. |
82-
| `short` | `SHORT` | |
83-
| `int` | `INTEGER` | |
84-
| `int` | `VARINT` | [LEB128](https://en.wikipedia.org/wiki/LEB128) encoded. Uses between 1 and 5 bytes. |
85-
| `long` | `LONG` | |
86-
| `float` | `FLOAT` | |
87-
| `double` | `DOUBLE` | |
88-
| `String` | `STRING` | Encoded as UTF-8. Length-prefixed with a varint. |
89-
| `UUID` | `UUID` | Encoded as two 64-bit integers. |
90-
91-
### Templates
92-
Complex composite types can be created using the template syntax:
93-
94-
```java
95-
public record User(String name, Gender gender, int age) {
96-
public static final BinaryCodec<User> CODEC = BinaryTemplate.of(
97-
STRING, User::name,
98-
Gender.CODEC, User::gender,
99-
INTEGER, User::age,
100-
// include up to 20 fields (total)
101-
User::new);
70+
</details>
71+
72+
<details>
73+
<summary>Gradle (Groovy)</summary>
74+
<br>
75+
76+
```groovy
77+
dependencies {
78+
implementation 'dev.mudkip:alpine-json:0.2.0'
10279
}
10380
```
10481

105-
### Transformations
106-
Use these methods to map a codec to another type.
107-
- `.array()` → `T[]`
108-
- `.list()` → `List<T>`
109-
- `.nullable()` → `@Nullable T`
110-
- `.optional()` → `Optional<T>`
111-
- `.map(Function<T, U>, Function<U, T>)` → `U`
112-
113-
### There's more!
114-
- Use `BinaryCodec.ordinal(Example.class)` to represent an enum.
115-
- Use `BinaryCodec.unit(Example::new)` to represent singleton types.
116-
- Use `BinaryCodec.map(keyCodec, valueCodec)` to represent a hash map.
117-
- Use `BinaryCodec.either(leftCodec, rightCodec)` to represent something which can be one of two types.
118-
- Use `BinaryCodec.of(Function<ByteBuf, T>, BiConsumer<ByteBuf, T>)` for an easier way to create a codec, especially if using the `::` syntax.
82+
</details>
83+
84+
<details>
85+
<summary>Maven</summary>
86+
<br>
87+
88+
```xml
89+
<dependency>
90+
<groupId>dev.mudkip</groupId>
91+
<artifactId>alpine-json</artifactId>
92+
<version>0.2.0</version>
93+
</dependency>
94+
```
95+
96+
</details>
97+
98+
## Documentation
99+
- [Binary](./binary.md)
100+
- [JSON](./json.md)

.github/binary.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Documentation
2+
The core primitive of Alpine is a codec. A codec is something that can encode and decode an object from a byte buffer.
3+
Netty's `ByteBuf` is used for this, however you don't need any other parts of Netty to take advantage of this system.
4+
5+
You can easily create an `Integer` codec like this:
6+
```java
7+
public static final BinaryCodec<Integer> INTEGER = new BinaryCodec<>() {
8+
@Override
9+
public Integer read(ByteBuf buffer) {
10+
return buffer.readInt();
11+
}
12+
13+
@Override
14+
public void write(ByteBuf buffer, Integer value) {
15+
buffer.writeInt(value);
16+
}
17+
};
18+
```
19+
20+
### Built-in codecs
21+
There are already many built-in codecs exposed through the `BinaryCodec` class, a partial list is available below:
22+
23+
| Java Type | Codec | Notes |
24+
|-------------|-------------------|-------------------------------------------------------------------------------------|
25+
| `boolean` | `BOOLEAN` | Encoded as `0` or `1`. |
26+
| `byte` | `BYTE` | |
27+
| `char` | `CHARACTER` | Encoded as a two-byte UTF-16 character. |
28+
| `short` | `SHORT` | |
29+
| `int` | `INTEGER` | |
30+
| `int` | `VARINT` | [LEB128](https://en.wikipedia.org/wiki/LEB128) encoded. Uses between 1 and 5 bytes. |
31+
| `long` | `LONG` | |
32+
| `float` | `FLOAT` | |
33+
| `double` | `DOUBLE` | |
34+
| `String` | `STRING` | Encoded as UTF-8. Length-prefixed with a varint. |
35+
| `UUID` | `UUID` | Encoded as two 64-bit integers. |
36+
37+
### Templates
38+
Complex composite types can be created using the template syntax:
39+
40+
```java
41+
public record User(String name, Gender gender, int age) {
42+
public static final BinaryCodec<User> CODEC = BinaryTemplate.of(
43+
STRING, User::name,
44+
Gender.CODEC, User::gender,
45+
INTEGER, User::age,
46+
// include up to 20 fields (total)
47+
User::new);
48+
}
49+
```
50+
51+
### Transformations
52+
Use these methods to map a codec to another type.
53+
- `.array()` → `T[]`
54+
- `.list()` → `List<T>`
55+
- `.nullable()` → `@Nullable T`
56+
- `.optional()` → `Optional<T>`
57+
- `.map(Function<T, U>, Function<U, T>)` → `U`
58+
59+
### There's more!
60+
- Use `BinaryCodec.ordinal(Example.class)` to represent an enum.
61+
- Use `BinaryCodec.unit(Example::new)` to represent singleton types.
62+
- Use `BinaryCodec.map(keyCodec, valueCodec)` to represent a hash map.
63+
- Use `BinaryCodec.either(leftCodec, rightCodec)` to represent something which can be one of two types.
64+
- Use `BinaryCodec.of(Function<ByteBuf, T>, BiConsumer<ByteBuf, T>)` for an easier way to create a codec, especially if using the `::` syntax.

.github/json.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Documentation
2+
This is not finished yet. [Take a look at the source code?](../json/src/main/java/alpine/json)

.github/renovate.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

.github/workflows/build.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,5 @@ jobs:
3030
with:
3131
name: alpine
3232
path: |
33-
binary/build/libs/alpine-*.*.*.jar
33+
binary/build/libs/binary-*.*.*.jar
34+
json/build/libs/json-*.*.*.jar

benchmark/build.gradle.kts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
plugins {
2+
id("me.champeau.jmh") version "0.7.3"
3+
}
4+
5+
dependencies {
6+
jmhImplementation(project(":json"))
7+
jmhImplementation("org.openjdk.jmh:jmh-core:1.37")
8+
jmhImplementation("org.openjdk.jmh:jmh-generator-annprocess:1.37")
9+
jmhImplementation("com.google.code.gson:gson:2.13.2")
10+
jmhImplementation("tools.jackson.core:jackson-databind:3.0.4")
11+
jmhImplementation("com.alibaba.fastjson2:fastjson2:2.0.60")
12+
}
13+
14+
jmh {
15+
warmupIterations.set(5)
16+
iterations.set(5)
17+
fork.set(1)
18+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package alpine.json;
2+
3+
import com.alibaba.fastjson2.JSON;
4+
import com.google.gson.JsonParser;
5+
import org.openjdk.jmh.annotations.*;
6+
import org.openjdk.jmh.infra.Blackhole;
7+
import tools.jackson.databind.ObjectMapper;
8+
9+
import java.util.concurrent.TimeUnit;
10+
11+
@State(Scope.Thread)
12+
@BenchmarkMode(Mode.AverageTime)
13+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
14+
@Warmup(iterations = 5, time = 1)
15+
@Measurement(iterations = 5, time = 1)
16+
public class JsonReadBenchmark {
17+
private static final ObjectMapper JACKSON = new ObjectMapper();
18+
19+
// Pool of 8 varied inputs (power of 2 for cheap index masking).
20+
// Different string lengths, number values, array sizes and nesting depth
21+
// prevent the branch predictor from memorizing a single character sequence.
22+
private static final String[] INPUTS = {
23+
"""
24+
{"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}}
25+
""",
26+
"""
27+
{"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}}
28+
""",
29+
"""
30+
{"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}}
31+
""",
32+
"""
33+
{"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}}
34+
""",
35+
"""
36+
{"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}}
37+
""",
38+
"""
39+
{"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}}
40+
""",
41+
"""
42+
{"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}}
43+
""",
44+
"""
45+
{"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}}
46+
"""
47+
};
48+
49+
private int index = 0;
50+
51+
@Benchmark
52+
public void alpine(Blackhole blackhole) throws Exception {
53+
blackhole.consume(Json.read(INPUTS[this.index++ % INPUTS.length]));
54+
}
55+
56+
@Benchmark
57+
public void gson(Blackhole blackhole) throws Exception {
58+
blackhole.consume(JsonParser.parseString(INPUTS[this.index++ % INPUTS.length]));
59+
}
60+
61+
@Benchmark
62+
public void jackson(Blackhole blackhole) throws Exception {
63+
blackhole.consume(JACKSON.readTree(INPUTS[this.index++ % INPUTS.length]));
64+
}
65+
66+
@Benchmark
67+
public void fastjson(Blackhole blackhole) throws Exception {
68+
blackhole.consume(JSON.parseObject(INPUTS[this.index++ % INPUTS.length]));
69+
}
70+
}

0 commit comments

Comments
 (0)