Skip to content

Commit ced5331

Browse files
committed
- add jackson 2 implementation
1 parent ecb3a1c commit ced5331

File tree

7 files changed

+302
-0
lines changed

7 files changed

+302
-0
lines changed

modules/jooby-jackson/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@
7171
<version>1.37</version>
7272
<scope>test</scope>
7373
</dependency>
74+
<dependency>
75+
<groupId>jakarta.json.bind</groupId>
76+
<artifactId>jakarta.json.bind-api</artifactId>
77+
</dependency>
7478
</dependencies>
7579

7680
<build>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.internal.jackson;
7+
8+
import java.io.IOException;
9+
10+
import com.fasterxml.jackson.databind.ObjectReader;
11+
import io.jooby.SneakyThrows;
12+
import io.jooby.trpc.TrpcDecoder;
13+
14+
public class JacksonTrpcDecoder<T> implements TrpcDecoder<T> {
15+
16+
final ObjectReader reader;
17+
18+
public JacksonTrpcDecoder(ObjectReader reader) {
19+
this.reader = reader;
20+
}
21+
22+
@Override
23+
public T decode(String name, byte[] payload) {
24+
try {
25+
return reader.readValue(payload);
26+
} catch (IOException x) {
27+
throw SneakyThrows.propagate(x);
28+
}
29+
}
30+
31+
@Override
32+
public T decode(String name, String payload) {
33+
try {
34+
return reader.readValue(payload);
35+
} catch (IOException x) {
36+
throw SneakyThrows.propagate(x);
37+
}
38+
}
39+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.internal.jackson;
7+
8+
import java.io.IOException;
9+
import java.lang.reflect.Type;
10+
11+
import com.fasterxml.jackson.databind.DeserializationFeature;
12+
import com.fasterxml.jackson.databind.ObjectMapper;
13+
import io.jooby.SneakyThrows;
14+
import io.jooby.trpc.TrpcDecoder;
15+
import io.jooby.trpc.TrpcParser;
16+
import io.jooby.trpc.TrpcReader;
17+
18+
public class JacksonTrpcParser implements TrpcParser {
19+
20+
private final ObjectMapper mapper;
21+
22+
public JacksonTrpcParser(ObjectMapper mapper) {
23+
this.mapper = mapper;
24+
}
25+
26+
@Override
27+
public <T> TrpcDecoder<T> decoder(Type type) {
28+
// Resolve the type exactly once at startup
29+
var javaType = mapper.constructType(type);
30+
var reader = mapper.readerFor(javaType).without(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
31+
return new JacksonTrpcDecoder<>(reader);
32+
}
33+
34+
@Override
35+
public TrpcReader reader(byte[] payload, boolean isTuple) {
36+
try {
37+
return new JacksonTrpcReader(mapper.createParser(payload), isTuple);
38+
} catch (IOException e) {
39+
throw SneakyThrows.propagate(e);
40+
}
41+
}
42+
43+
@Override
44+
public TrpcReader reader(String payload, boolean isTuple) {
45+
try {
46+
return new JacksonTrpcReader(mapper.createParser(payload), isTuple);
47+
} catch (IOException e) {
48+
throw SneakyThrows.propagate(e);
49+
}
50+
}
51+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.internal.jackson;
7+
8+
import java.io.IOException;
9+
10+
import com.fasterxml.jackson.core.JsonParser;
11+
import com.fasterxml.jackson.core.JsonToken;
12+
import io.jooby.SneakyThrows;
13+
import io.jooby.exception.MissingValueException;
14+
import io.jooby.trpc.TrpcDecoder;
15+
import io.jooby.trpc.TrpcReader;
16+
17+
public class JacksonTrpcReader implements TrpcReader {
18+
private final JsonParser parser;
19+
private boolean hasPeeked = false;
20+
private final boolean isTuple;
21+
private boolean isFirstRead = true;
22+
23+
public JacksonTrpcReader(JsonParser parser, boolean isTuple) {
24+
this.parser = parser;
25+
this.isTuple = isTuple;
26+
var token = nextToken();
27+
if (isTuple && token != JsonToken.START_ARRAY) {
28+
throw new IllegalArgumentException("Expected tRPC tuple array");
29+
}
30+
}
31+
32+
private JsonToken nextToken() {
33+
try {
34+
return parser.nextToken();
35+
} catch (IOException e) {
36+
throw SneakyThrows.propagate(e);
37+
}
38+
}
39+
40+
@Override
41+
public boolean nextIsNull(String name) {
42+
if (!hasPeeked) {
43+
advance(name);
44+
hasPeeked = true;
45+
}
46+
47+
if (parser.currentToken() == JsonToken.VALUE_NULL) {
48+
hasPeeked = false; // Consume the null token
49+
return true;
50+
}
51+
52+
return false;
53+
}
54+
55+
private void ensureNext(String name) {
56+
if (hasPeeked) {
57+
hasPeeked = false;
58+
return;
59+
}
60+
advance(name);
61+
}
62+
63+
private void ensureNonNull(String name) {
64+
if (parser.currentToken() == JsonToken.VALUE_NULL) throw new MissingValueException(name);
65+
}
66+
67+
private void advance(String name) {
68+
// If it's a seamless raw value, we are ALREADY on the token. Do not advance.
69+
if (!isTuple) {
70+
if (!isFirstRead) throw new MissingValueException(name);
71+
isFirstRead = false;
72+
// The constructor already positioned us on the root token. Do not advance.
73+
return;
74+
}
75+
76+
var token = nextToken();
77+
if (token == JsonToken.END_ARRAY || token == null) {
78+
throw new MissingValueException(name);
79+
}
80+
}
81+
82+
@Override
83+
public int nextInt(String name) {
84+
ensureNext(name);
85+
ensureNonNull(name);
86+
try {
87+
return parser.getIntValue();
88+
} catch (IOException e) {
89+
throw SneakyThrows.propagate(e);
90+
}
91+
}
92+
93+
@Override
94+
public long nextLong(String name) {
95+
ensureNext(name);
96+
ensureNonNull(name);
97+
try {
98+
return parser.getLongValue();
99+
} catch (IOException e) {
100+
throw SneakyThrows.propagate(e);
101+
}
102+
}
103+
104+
@Override
105+
public boolean nextBoolean(String name) {
106+
ensureNext(name);
107+
ensureNonNull(name);
108+
try {
109+
return parser.getBooleanValue();
110+
} catch (IOException e) {
111+
throw SneakyThrows.propagate(e);
112+
}
113+
}
114+
115+
@Override
116+
public double nextDouble(String name) {
117+
ensureNext(name);
118+
ensureNonNull(name);
119+
try {
120+
return parser.getDoubleValue();
121+
} catch (IOException e) {
122+
throw SneakyThrows.propagate(e);
123+
}
124+
}
125+
126+
@Override
127+
public String nextString(String name) {
128+
ensureNext(name);
129+
ensureNonNull(name);
130+
try {
131+
return parser.getText();
132+
} catch (IOException e) {
133+
throw SneakyThrows.propagate(e);
134+
}
135+
}
136+
137+
@Override
138+
public <T> T nextObject(String name, TrpcDecoder<T> decoder) {
139+
try {
140+
ensureNext(name);
141+
ensureNonNull(name);
142+
JacksonTrpcDecoder<T> jacksonDecoder = (JacksonTrpcDecoder<T>) decoder;
143+
return jacksonDecoder.reader.readValue(parser);
144+
} catch (IOException e) {
145+
throw SneakyThrows.propagate(e);
146+
}
147+
}
148+
149+
@Override
150+
public void close() throws Exception {
151+
parser.close();
152+
}
153+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.internal.jackson;
7+
8+
import java.io.IOException;
9+
10+
import com.fasterxml.jackson.core.JsonGenerator;
11+
import com.fasterxml.jackson.databind.SerializerProvider;
12+
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
13+
import io.jooby.trpc.TrpcResponse;
14+
15+
public class JacksonTrpcResponseSerializer extends StdSerializer<TrpcResponse> {
16+
public JacksonTrpcResponseSerializer() {
17+
super(TrpcResponse.class);
18+
}
19+
20+
@Override
21+
public void serialize(TrpcResponse value, JsonGenerator gen, SerializerProvider provider)
22+
throws IOException {
23+
gen.writeStartObject(); // {
24+
25+
gen.writeFieldName("result");
26+
gen.writeStartObject(); // "result": {
27+
28+
var data = value.data();
29+
// Only write the "data" key if the method actually returned something (not void/Unit)
30+
if (data != null) {
31+
gen.writeFieldName("data");
32+
gen.writePOJO(data);
33+
}
34+
35+
gen.writeEndObject();
36+
gen.writeEndObject();
37+
}
38+
}

modules/jooby-jackson/src/main/java/io/jooby/jackson/JacksonModule.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@
3030
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
3131
import edu.umd.cs.findbugs.annotations.NonNull;
3232
import io.jooby.*;
33+
import io.jooby.internal.jackson.JacksonTrpcParser;
34+
import io.jooby.internal.jackson.JacksonTrpcResponseSerializer;
3335
import io.jooby.output.Output;
36+
import io.jooby.trpc.TrpcParser;
37+
import io.jooby.trpc.TrpcResponse;
3438

3539
/**
3640
* JSON module using Jackson: https://jooby.io/modules/jackson2.
@@ -150,6 +154,9 @@ public void install(@NonNull Jooby application) {
150154
application.errorCode(JsonParseException.class, StatusCode.BAD_REQUEST);
151155
application.errorCode(MismatchedInputException.class, StatusCode.BAD_REQUEST);
152156

157+
// tRPC
158+
services.put(TrpcParser.class, new JacksonTrpcParser(mapper));
159+
153160
// Filter
154161
var defaultProvider = new SimpleFilterProvider().setFailOnUnknownId(false);
155162
mapper.addMixIn(Object.class, ProjectionMixIn.class);
@@ -221,6 +228,10 @@ public Object decode(Context ctx, Type type) throws Exception {
221228
.addModule(new JavaTimeModule());
222229

223230
Stream.of(modules).forEach(builder::addModule);
231+
// tRPC
232+
var trpcModule = new SimpleModule();
233+
trpcModule.addSerializer(TrpcResponse.class, new JacksonTrpcResponseSerializer());
234+
builder.addModule(trpcModule);
224235

225236
return builder.build();
226237
}

tests/src/test/java/io/jooby/i3863/TrpcProtocolTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.jayway.jsonpath.JsonPath;
1313
import io.jooby.Extension;
1414
import io.jooby.avaje.jsonb.AvajeJsonbModule;
15+
import io.jooby.jackson.JacksonModule;
1516
import io.jooby.jackson3.Jackson3Module;
1617
import io.jooby.junit.ServerTest;
1718
import io.jooby.junit.ServerTestRunner;
@@ -25,6 +26,11 @@ void shouldTalkTrpcUsingJackson3(ServerTestRunner runner) {
2526
shouldTalkTrpc(runner, new Jackson3Module());
2627
}
2728

29+
@ServerTest
30+
void shouldTalkTrpcUsingJackson2(ServerTestRunner runner) {
31+
shouldTalkTrpc(runner, new JacksonModule());
32+
}
33+
2834
@ServerTest
2935
void shouldTalkTrpcUsingAvajeJsonB(ServerTestRunner runner) {
3036
shouldTalkTrpc(runner, new AvajeJsonbModule());

0 commit comments

Comments
 (0)