forked from ngs-doo/dsl-json
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Example.java
260 lines (228 loc) · 10.2 KB
/
Example.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
package com.dslplatform.maven;
import com.dslplatform.json.*;
import com.dslplatform.json.runtime.MapAnalyzer;
import com.dslplatform.json.runtime.Settings;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalTime;
import java.util.*;
public class Example {
@CompiledJson(onUnknown = CompiledJson.Behavior.IGNORE) //ignore unknown properties (default for objects). to disallow unknown properties in JSON set it to FAIL which will result in exception instead
static class Model { //package private visibility is supported in Java8 version
@JsonAttribute(nullable = false) //indicate that field can't be null
public String string;
public List<Integer> integers;
@JsonAttribute(name = "guids") //use alternative name in JSON
public UUID[] uuids;
public Set<BigDecimal> decimals;
public Vector<Long> longs;
@JsonAttribute(hashMatch = false) // exact name match can be forced, otherwise hash value will be used for matching
public int number;
@JsonAttribute(alternativeNames = {"old_nested", "old_nested2"}) //several JSON attribute names can be deserialized into this field
public List<Nested> nested;
@JsonAttribute(typeSignature = CompiledJson.TypeSignature.EXCLUDE) //$type attribute can be excluded from resulting JSON
public Abstract abs;//abstract classes or interfaces can be used which will also include $type attribute in JSON by default
public List<Abstract> absList;
public Interface iface;//interfaces without deserializedAs will also include $type attribute in JSON by default
public ParentClass inheritance;
@JsonAttribute(mandatory = true)// mandatory adds check if property exist in JSON and will serialize it even in omit-defaults mode
public List<State> states;
public JsonObjectReference jsonObject; //object implementing JsonObject manage their own conversion. They must start with '{'
public List<JsonObjectReference> jsonObjects;
@JsonAttribute(ignore = true)
public GregorianCalendar ignored;
public LocalTime time; //LocalTime is not supported, but with the use of converter it will work
public List<LocalTime> times; //even containers with unsupported type will be resolved
@JsonAttribute(converter = FormatDecimal2.class)
public BigDecimal decimal2; //custom formatting can be implemented with per property converters
public ArrayList<Integer> intList; //most collections are supported through runtime converters
//since this signature has an unknown part (Object), it must be whitelisted
//This can be done via appropriate converter, by registering @JsonConverter for the specified type
//or by enabling support for unknown types in the annotation processor
@JsonAttribute(converter = MapAnalyzer.Runtime.class)
public Map<String, Object> map;
public ImmutablePerson person; //immutable objects are supported via several patterns (in this case ctor with arguments)
public List<ViaFactory> factories; //objects without accessible constructor can be created through factory methods
public PersonBuilder builder; //builder pattern is supported
public List<SpecialNumber> numbers;//enum with specific values
public MutablePerson binding = new MutablePerson();
//explicitly referenced classes don't require @CompiledJson annotation
public static class Nested {
public long x;
public double y;
public float z;
}
@CompiledJson(deserializeAs = Concrete.class)//without deserializeAs deserializing Abstract would fails since it doesn't contain a $type due to it's exclusion in the above configuration
public static abstract class Abstract {
public int x;
}
//since this class is not explicitly referenced, but it's an extension of the abstract class used as a property
//it needs to be decorated with annotation
@CompiledJson
public static class Concrete extends Abstract {
public long y;
}
public interface Interface {
void x(int v);
int x();
}
@CompiledJson(name = "custom-name")//by default class name will be used for $type attribute
public static class WithCustomCtor implements Interface {
private int x;
private int y;
public WithCustomCtor(int x) {
this.x = x;
this.y = x;
}
@CompiledJson
public WithCustomCtor(int x, int y) {
this.x = x;
this.y = y;
}
public void x(int v) { x = v; }
public int x() { return x; }
public void setY(int v) { y = v; }
public int getY() { return y; }
}
public static class ViaFactory {
public final String name;
public final int num;
private ViaFactory(String name, int num) {
this.name = name;
this.num = num;
}
//compiled json can also be used on factory methods when ctor is not available
@CompiledJson
public static ViaFactory create(String name, int num) {
return new ViaFactory(name, num);
}
}
public static class BaseClass {
public int a;
}
public static class ParentClass extends BaseClass {
public long b;
}
public enum State {
LOW(0),
MID(1),
HI(2);
private final int value;
State(int value) {
this.value = value;
}
}
public static class JsonObjectReference implements JsonObject {
public final int x;
public final String s;
JsonObjectReference(int x, String s) {
this.x = x;
this.s = s;
}
public void serialize(JsonWriter writer, boolean minimal) {
writer.writeAscii("{\"x\":");
NumberConverter.serialize(x, writer);
writer.writeAscii(",\"s\":");
StringConverter.serialize(s, writer);
writer.writeAscii("}");
}
public static final JsonReader.ReadJsonObject<JsonObjectReference> JSON_READER = new JsonReader.ReadJsonObject<JsonObjectReference>() {
public JsonObjectReference deserialize(JsonReader reader) throws IOException {
reader.fillName();//"x"
reader.getNextToken();//start number
int x = NumberConverter.deserializeInt(reader);
reader.getNextToken();//,
reader.getNextToken();//start name
reader.fillName();//"s"
reader.getNextToken();//start string
String s = StringConverter.deserialize(reader);
reader.getNextToken();//}
return new JsonObjectReference(x, s);
}
};
}
public static abstract class FormatDecimal2 {
public static BigDecimal read(JsonReader reader) throws IOException {
if (reader.wasNull()) return null;
return NumberConverter.deserializeDecimal(reader).setScale(2);
}
public static void write(JsonWriter writer, BigDecimal value) {
if (value == null) {
writer.writeNull();
} else {
NumberConverter.serializeNullable(value.setScale(2), writer);
}
}
}
}
//moved outside of private package class since it reference object in java namespace
@JsonConverter(target = LocalTime.class)
public static abstract class LocalTimeConverter {
public static LocalTime read(JsonReader reader) throws IOException {
if (reader.wasNull()) return null;
return LocalTime.parse(reader.readSimpleString());
}
public static void write(JsonWriter writer, LocalTime value) {
if (value == null) {
writer.writeNull();
} else {
writer.writeString(value.toString());
}
}
}
public static void main(String[] args) throws IOException {
//Model converters will be loaded based on naming convention.
//Previously it would be loaded through ServiceLoader.load,
//which is still an option if dsljson.configuration name is specified.
//DSL-JSON loads all services registered into META-INF/services
//and falls back to naming based convention of package._NAME_DslJsonConfiguration if not found
//withRuntime is enabled to support runtime analysis for stuff which is not registered by default
//Annotation processor will run by default and generate descriptions for JSON encoding/decoding
DslJson<Object> dslJson = new DslJson<>(Settings.withRuntime().allowArrayFormat(true).includeServiceLoader());
//writer should be reused. For per thread reuse use ThreadLocal pattern
JsonWriter writer = dslJson.newWriter();
Model instance = new Model();
instance.string = "Hello World!";
instance.number = 42;
instance.integers = Arrays.asList(1, 2, 3);
instance.decimals = new HashSet<>(Arrays.asList(BigDecimal.ONE, BigDecimal.ZERO));
instance.uuids = new UUID[]{new UUID(1L, 2L), new UUID(3L, 4L)};
instance.longs = new Vector<>(Arrays.asList(1L, 2L));
instance.nested = Arrays.asList(new Model.Nested(), null);
instance.inheritance = new Model.ParentClass();
instance.inheritance.a = 5;
instance.inheritance.b = 6;
instance.iface = new Model.WithCustomCtor(5, 6);
instance.person = new ImmutablePerson("first name", "last name", 35);
instance.states = Arrays.asList(Model.State.HI, Model.State.LOW);
instance.jsonObject = new Model.JsonObjectReference(43, "abcd");
instance.jsonObjects = Collections.singletonList(new Model.JsonObjectReference(34, "dcba"));
instance.time = LocalTime.of(12, 15);
instance.times = Arrays.asList(null, LocalTime.of(8, 16));
Model.Concrete concrete = new Model.Concrete();
concrete.x = 11;
concrete.y = 23;
instance.abs = concrete;
instance.absList = Arrays.<Model.Abstract>asList(concrete, null, concrete);
instance.decimal2 = BigDecimal.TEN;
instance.intList = new ArrayList<>(Arrays.asList(123, 456));
instance.map = new HashMap<>();
instance.map.put("abc", 678);
instance.map.put("array", new int[] { 2, 4, 8});
instance.factories = Arrays.asList(null, Model.ViaFactory.create("me", 2), Model.ViaFactory.create("you", 3), null);
instance.builder = PersonBuilder.builder().firstName("first").lastName("last").age(42).build();
instance.numbers = Arrays.asList(SpecialNumber.E, SpecialNumber.PI, SpecialNumber.ZERO);
instance.binding.firstname = MutablePerson.Name.create("my name");
instance.binding.surname = MutablePerson.Name.create("your surname");
instance.binding.age = 99;
dslJson.serialize(writer, instance);
//resulting buffer with JSON
byte[] buffer = writer.getByteBuffer();
//end of buffer
int size = writer.size();
System.out.println(writer);
//deserialization using byte[] API
Model deser = dslJson.deserialize(Model.class, buffer, size);
System.out.println(deser.string);
}
}