Skip to content

Commit 9701b6e

Browse files
authored
feature Encoder/Decoder supports Fastjson2 (#2368)
* feature Encoder/Decoder supports Fastjson2 * add Fastjson2 to the mind map
1 parent 9622a79 commit 9701b6e

File tree

9 files changed

+450
-0
lines changed

9 files changed

+450
-0
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,23 @@ public class Example {
474474
475475
NB: you may also need to add `SOAPErrorDecoder` if SOAP Faults are returned in response with error http codes (4xx, 5xx, ...)
476476
477+
#### Fastjson2
478+
479+
[fastjson2](./fastjson2) includes an encoder and decoder you can use with a JSON API.
480+
481+
Add `Fastjson2Encoder` and/or `Fastjson2Decoder` to your `Feign.Builder` like so:
482+
483+
```java
484+
public class Example {
485+
public static void main(String[] args) {
486+
GitHub github = Feign.builder()
487+
.encoder(new Fastjson2Encoder())
488+
.decoder(new Fastjson2Decoder())
489+
.target(GitHub.class, "https://api.github.com");
490+
}
491+
}
492+
```
493+
477494
### Contract
478495
479496
#### JAX-RS

fastjson2/README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
Fastjson2 Codec
2+
===================
3+
4+
This module adds support for encoding and decoding JSON via Fastjson2.
5+
6+
Add `Fastjson2Encoder` and/or `Fastjson2Decoder` to your `Feign.Builder` like so:
7+
8+
```java
9+
GitHub github = Feign.builder()
10+
.encoder(new Fastjson2Encoder())
11+
.decoder(new Fastjson2Decoder())
12+
.target(GitHub.class, "https://api.github.com");
13+
```
14+
15+
If you want to customize, provide it to the `Fastjson2Encoder` and `Fastjson2Decoder`:
16+
17+
```java
18+
GitHub github = Feign.builder()
19+
.encoder(new Fastjson2Encoder(new JSONWriter.Feature[]{JSONWriter.Feature.WriteNonStringValueAsString})
20+
.decoder(new Fastjson2Decoder(new JSONReader.Feature[]{JSONReader.Feature.EmptyStringAsNull}))
21+
.target(GitHub.class, "https://api.github.com");
22+
```

fastjson2/pom.xml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Copyright 2012-2024 The Feign Authors
5+
6+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7+
in compliance with the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software distributed under the License
12+
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13+
or implied. See the License for the specific language governing permissions and limitations under
14+
the License.
15+
16+
-->
17+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
18+
<modelVersion>4.0.0</modelVersion>
19+
20+
<parent>
21+
<groupId>io.github.openfeign</groupId>
22+
<artifactId>parent</artifactId>
23+
<version>13.3-SNAPSHOT</version>
24+
</parent>
25+
26+
<artifactId>feign-fastjson2</artifactId>
27+
<name>Feign Fastjson2</name>
28+
<description>Feign Fastjson2</description>
29+
30+
<properties>
31+
<main.basedir>${project.basedir}/..</main.basedir>
32+
</properties>
33+
34+
<dependencies>
35+
<dependency>
36+
<groupId>${project.groupId}</groupId>
37+
<artifactId>feign-core</artifactId>
38+
</dependency>
39+
40+
<dependency>
41+
<groupId>com.alibaba.fastjson2</groupId>
42+
<artifactId>fastjson2</artifactId>
43+
</dependency>
44+
45+
<dependency>
46+
<groupId>${project.groupId}</groupId>
47+
<artifactId>feign-core</artifactId>
48+
<type>test-jar</type>
49+
<scope>test</scope>
50+
</dependency>
51+
</dependencies>
52+
</project>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2012-2024 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.fastjson2;
15+
16+
import com.alibaba.fastjson2.JSON;
17+
import com.alibaba.fastjson2.JSONException;
18+
import com.alibaba.fastjson2.JSONReader;
19+
import feign.FeignException;
20+
import feign.Response;
21+
import feign.Util;
22+
import feign.codec.Decoder;
23+
import java.io.IOException;
24+
import java.io.Reader;
25+
import java.lang.reflect.Type;
26+
import static feign.Util.ensureClosed;
27+
28+
/**
29+
* @author changjin wei(魏昌进)
30+
*/
31+
public class Fastjson2Decoder implements Decoder {
32+
33+
private final JSONReader.Feature[] features;
34+
35+
public Fastjson2Decoder() {
36+
this(new JSONReader.Feature[0]);
37+
}
38+
39+
public Fastjson2Decoder(JSONReader.Feature[] features) {
40+
this.features = features;
41+
}
42+
43+
@Override
44+
public Object decode(Response response, Type type) throws IOException, FeignException {
45+
if (response.status() == 404 || response.status() == 204)
46+
return Util.emptyValueOf(type);
47+
if (response.body() == null)
48+
return null;
49+
Reader reader = response.body().asReader(response.charset());
50+
try {
51+
return JSON.parseObject(reader, type, features);
52+
} catch (JSONException e) {
53+
if (e.getCause() != null && e.getCause() instanceof IOException) {
54+
throw IOException.class.cast(e.getCause());
55+
}
56+
throw e;
57+
} finally {
58+
ensureClosed(reader);
59+
}
60+
}
61+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2012-2024 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.fastjson2;
15+
16+
import com.alibaba.fastjson2.JSON;
17+
import com.alibaba.fastjson2.JSONWriter;
18+
import feign.RequestTemplate;
19+
import feign.Util;
20+
import feign.codec.EncodeException;
21+
import feign.codec.Encoder;
22+
import java.lang.reflect.Type;
23+
24+
/**
25+
* @author changjin wei(魏昌进)
26+
*/
27+
public class Fastjson2Encoder implements Encoder {
28+
29+
private final JSONWriter.Feature[] features;
30+
31+
public Fastjson2Encoder() {
32+
this(new JSONWriter.Feature[0]);
33+
}
34+
35+
public Fastjson2Encoder(JSONWriter.Feature[] features) {
36+
this.features = features;
37+
}
38+
39+
@Override
40+
public void encode(Object object, Type bodyType, RequestTemplate template)
41+
throws EncodeException {
42+
template.body(JSON.toJSONBytes(object, features), Util.UTF_8);
43+
}
44+
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/*
2+
* Copyright 2012-2024 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.fastjson2;
15+
16+
import static feign.Util.UTF_8;
17+
import static feign.assertj.FeignAssertions.assertThat;
18+
import java.io.IOException;
19+
import java.nio.charset.StandardCharsets;
20+
import java.util.Arrays;
21+
import java.util.Collection;
22+
import java.util.Collections;
23+
import java.util.HashMap;
24+
import java.util.LinkedHashMap;
25+
import java.util.LinkedList;
26+
import java.util.List;
27+
import java.util.Map;
28+
import com.alibaba.fastjson2.TypeReference;
29+
import org.junit.jupiter.api.Test;
30+
import feign.Request;
31+
import feign.RequestTemplate;
32+
import feign.Response;
33+
import feign.Util;
34+
35+
/**
36+
* @author changjin wei(魏昌进)
37+
*/
38+
@SuppressWarnings("deprecation")
39+
class FastJsonCodecTest {
40+
41+
private String zonesJson = ""//
42+
+ "[" + System.lineSeparator() //
43+
+ " {" + System.lineSeparator() //
44+
+ " \"name\": \"denominator.io.\"" + System.lineSeparator()//
45+
+ " }," + System.lineSeparator()//
46+
+ " {" + System.lineSeparator()//
47+
+ " \"name\": \"denominator.io.\"," + System.lineSeparator()//
48+
+ " \"id\": \"ABCD\"" + System.lineSeparator()//
49+
+ " }" + System.lineSeparator()//
50+
+ "]" + System.lineSeparator();
51+
52+
@Test
53+
void encodesMapObjectNumericalValuesAsInteger() {
54+
Map<String, Object> map = new LinkedHashMap<>();
55+
map.put("foo", 1);
56+
57+
RequestTemplate template = new RequestTemplate();
58+
new Fastjson2Encoder().encode(map, map.getClass(), template);
59+
60+
assertThat(template).hasBody(""//
61+
+ "{" //
62+
+ "\"foo\":1" //
63+
+ "}");
64+
}
65+
66+
@Test
67+
void encodesFormParams() {
68+
Map<String, Object> form = new LinkedHashMap<>();
69+
form.put("foo", 1);
70+
form.put("bar", Arrays.asList(2, 3));
71+
72+
RequestTemplate template = new RequestTemplate();
73+
new Fastjson2Encoder().encode(form, new TypeReference<Map<String, ?>>() {}.getType(), template);
74+
75+
assertThat(template).hasBody(""//
76+
+ "{" //
77+
+ "\"foo\":1,"//
78+
+ "\"bar\":[2,3]"//
79+
+ "}");
80+
}
81+
82+
@Test
83+
void decodes() throws Exception {
84+
List<Zone> zones = new LinkedList<>();
85+
zones.add(new Zone("denominator.io."));
86+
zones.add(new Zone("denominator.io.", "ABCD"));
87+
88+
Response response = Response.builder()
89+
.status(200)
90+
.reason("OK")
91+
.request(Request.create(Request.HttpMethod.GET, "/api", Collections.emptyMap(), null,
92+
Util.UTF_8))
93+
.headers(Collections.emptyMap())
94+
.body(zonesJson, UTF_8)
95+
.build();
96+
assertThat(
97+
new Fastjson2Decoder().decode(response, new TypeReference<List<Zone>>() {}.getType()))
98+
.isEqualTo(zones);
99+
}
100+
101+
@Test
102+
void nullBodyDecodesToNull() throws Exception {
103+
Response response = Response.builder()
104+
.status(204)
105+
.reason("OK")
106+
.request(Request.create(Request.HttpMethod.GET, "/api", Collections.emptyMap(), null,
107+
Util.UTF_8))
108+
.headers(Collections.emptyMap())
109+
.build();
110+
assertThat(new Fastjson2Decoder().decode(response, String.class)).isNull();
111+
}
112+
113+
@Test
114+
void emptyBodyDecodesToNull() throws Exception {
115+
Response response = Response.builder()
116+
.status(204)
117+
.reason("OK")
118+
.request(Request.create(Request.HttpMethod.GET, "/api", Collections.emptyMap(), null,
119+
Util.UTF_8))
120+
.headers(Collections.emptyMap())
121+
.body(new byte[0])
122+
.build();
123+
assertThat(new Fastjson2Decoder().decode(response, String.class)).isNull();
124+
}
125+
126+
127+
@Test
128+
void decoderCharset() throws IOException {
129+
Zone zone = new Zone("denominator.io.", "ÁÉÍÓÚÀÈÌÒÙÄËÏÖÜÑ");
130+
131+
Map<String, Collection<String>> headers = new HashMap<>();
132+
headers.put("Content-Type", Arrays.asList("application/json;charset=ISO-8859-1"));
133+
134+
Response response = Response.builder()
135+
.status(200)
136+
.reason("OK")
137+
.request(Request.create(Request.HttpMethod.GET, "/api", Collections.emptyMap(), null,
138+
Util.UTF_8))
139+
.headers(headers)
140+
.body(new String("" //
141+
+ "{"
142+
+ " \"name\" : \"DENOMINATOR.IO.\","
143+
+ " \"id\" : \"ÁÉÍÓÚÀÈÌÒÙÄËÏÖÜÑ\""
144+
+ "}").getBytes(StandardCharsets.ISO_8859_1))
145+
.build();
146+
assertThat(
147+
((Zone) new Fastjson2Decoder().decode(response, new TypeReference<Zone>() {}.getType())))
148+
.containsEntry("id", zone.get("id"));
149+
}
150+
151+
static class Zone extends LinkedHashMap<String, Object> {
152+
153+
private static final long serialVersionUID = 1L;
154+
155+
Zone() {
156+
// for reflective instantiation.
157+
}
158+
159+
Zone(String name) {
160+
this(name, null);
161+
}
162+
163+
Zone(String name, String id) {
164+
put("name", name);
165+
if (id != null) {
166+
put("id", id);
167+
}
168+
}
169+
}
170+
171+
172+
173+
/** Enabled via {@link feign.Feign.Builder#dismiss404()} */
174+
@Test
175+
void notFoundDecodesToEmpty() throws Exception {
176+
Response response = Response.builder()
177+
.status(404)
178+
.reason("NOT FOUND")
179+
.request(Request.create(Request.HttpMethod.GET, "/api", Collections.emptyMap(), null,
180+
Util.UTF_8))
181+
.headers(Collections.emptyMap())
182+
.build();
183+
assertThat((byte[]) new Fastjson2Decoder().decode(response, byte[].class)).isEmpty();
184+
}
185+
186+
187+
}

0 commit comments

Comments
 (0)