|
1 | 1 | package io.github.cichlidmc.tinycodecs; |
2 | 2 |
|
| 3 | +import io.github.cichlidmc.tinycodecs.codec.ByNameCodec; |
| 4 | +import io.github.cichlidmc.tinycodecs.codec.DispatchCodec; |
| 5 | +import io.github.cichlidmc.tinycodecs.codec.EitherCodec; |
| 6 | +import io.github.cichlidmc.tinycodecs.codec.ListCodec; |
| 7 | +import io.github.cichlidmc.tinycodecs.codec.UnitCodec; |
| 8 | +import io.github.cichlidmc.tinycodecs.codec.map.CompositeCodec; |
| 9 | +import io.github.cichlidmc.tinycodecs.codec.map.FieldOfCodec; |
| 10 | +import io.github.cichlidmc.tinycodecs.codec.optional.DefaultedOptionalCodec; |
| 11 | +import io.github.cichlidmc.tinycodecs.codec.optional.OptionalCodec; |
| 12 | +import io.github.cichlidmc.tinycodecs.map.MapCodec; |
3 | 13 | import io.github.cichlidmc.tinycodecs.util.Either; |
| 14 | +import io.github.cichlidmc.tinyjson.JsonException; |
4 | 15 | import io.github.cichlidmc.tinyjson.value.JsonValue; |
| 16 | +import io.github.cichlidmc.tinyjson.value.primitive.JsonBool; |
| 17 | +import io.github.cichlidmc.tinyjson.value.primitive.JsonNumber; |
| 18 | +import io.github.cichlidmc.tinyjson.value.primitive.JsonString; |
| 19 | +import org.jetbrains.annotations.Nullable; |
5 | 20 |
|
| 21 | +import java.util.Collections; |
| 22 | +import java.util.HashMap; |
6 | 23 | import java.util.List; |
| 24 | +import java.util.Map; |
7 | 25 | import java.util.Optional; |
8 | 26 | import java.util.function.Function; |
9 | | -import java.util.function.Predicate; |
10 | 27 | import java.util.function.Supplier; |
11 | | -import java.util.function.UnaryOperator; |
12 | 28 |
|
13 | 29 | /** |
14 | | - * A Codec determines how an object is (de)serialized. |
15 | | - * It accepts and outputs any type of JSON. |
| 30 | + * A Codec is both an encoder and decoder of some type of object. |
| 31 | + * @see CompositeCodec |
16 | 32 | */ |
17 | | -public interface Codec<T> { |
18 | | - /** |
19 | | - * Attempt to decode the given JsonValue. |
20 | | - * Should never throw an exception. |
21 | | - * @param value the JSON to decode. A JsonNull may indicate either a literal null or a missing field |
22 | | - */ |
23 | | - CodecResult<T> decode(JsonValue value); |
24 | | - |
25 | | - /** |
26 | | - * Attempt to encode the given value to JSON. |
27 | | - * Should never throw an exception. |
28 | | - */ |
29 | | - CodecResult<? extends JsonValue> encode(T value); |
| 33 | +public interface Codec<T> extends Encoder<T>, Decoder<T> { |
| 34 | + // base implementations |
| 35 | + Codec<Boolean> BOOL = throwing(json -> json.asBoolean().value(), JsonBool::new); |
| 36 | + Codec<String> STRING = throwing(json -> json.asString().value(), JsonString::new); |
| 37 | + Codec<Byte> BYTE = number(Number::byteValue); |
| 38 | + Codec<Short> SHORT = number(Number::shortValue); |
| 39 | + Codec<Integer> INT = number(Number::intValue); |
| 40 | + Codec<Long> LONG = number(Number::longValue); |
| 41 | + Codec<Float> FLOAT = number(Number::floatValue); |
| 42 | + Codec<Double> DOUBLE = number(Number::doubleValue); |
30 | 43 |
|
31 | 44 | // transforms |
32 | | - |
33 | | - default Codec<T> validate(Predicate<T> validator) { |
34 | | - return Codecs.validate(this, validator); |
35 | | - } |
36 | 45 |
|
37 | | - default Codec<T> map(UnaryOperator<T> function) { |
38 | | - return Codecs.map(this, function); |
| 46 | + default Codec<T> validate(Function<? super T, ? extends CodecResult<T>> validator) { |
| 47 | + return this.flatXmap(validator, validator); |
39 | 48 | } |
40 | 49 |
|
41 | | - default Codec<T> mapResult(UnaryOperator<CodecResult<T>> function) { |
42 | | - return Codecs.mapResult(this, function); |
| 50 | + default <B> Codec<B> xmap(Function<? super T, ? extends B> to, Function<? super B, ? extends T> from) { |
| 51 | + return Codec.of(this.comap(from), this.map(to)); |
43 | 52 | } |
44 | 53 |
|
45 | | - default Codec<T> flatMap(Function<T, CodecResult<T>> function) { |
46 | | - return Codecs.flatMap(this, function); |
| 54 | + default <B> Codec<B> comapFlatMap(Function<? super T, ? extends CodecResult<? extends B>> to, Function<? super B, ? extends T> from) { |
| 55 | + return Codec.of(this.comap(from), this.flatMap(to)); |
47 | 56 | } |
48 | 57 |
|
49 | | - default <B> Codec<B> xmap(Function<? super T, ? extends B> aToB, Function<? super B, ? extends T> bToA) { |
50 | | - return Codecs.xmap(this, aToB, bToA); |
| 58 | + default <B> Codec<B> flatComapMap(Function<? super T, ? extends B> to, Function<? super B, ? extends CodecResult<? extends T>> from) { |
| 59 | + return Codec.of(this.flatComap(from), this.map(to)); |
51 | 60 | } |
52 | 61 |
|
53 | | - default <B> Codec<B> flatXmap(Function<T, CodecResult<B>> aToB, Function<B, T> bToA) { |
54 | | - return Codecs.flatXmap(this, aToB, bToA); |
| 62 | + default <B> Codec<B> flatXmap(Function<? super T, ? extends CodecResult<? extends B>> to, Function<? super B, ? extends CodecResult<? extends T>> from) { |
| 63 | + return Codec.of(this.flatComap(from), this.flatMap(to)); |
55 | 64 | } |
56 | 65 |
|
57 | 66 | default <B> Codec<B> dispatch(String key, Function<? super B, ? extends T> typeGetter, Function<? super T, ? extends MapCodec<? extends B>> codecGetter) { |
58 | | - return Codecs.dispatch(this, key, typeGetter, codecGetter); |
| 67 | + return new DispatchCodec<>(this, key, typeGetter, codecGetter); |
59 | 68 | } |
60 | 69 |
|
61 | 70 | default <B> Codec<B> dispatch(Function<? super B, ? extends T> typeGetter, Function<? super T, MapCodec<? extends B>> codecGetter) { |
62 | | - return Codecs.dispatch(this, typeGetter, codecGetter); |
| 71 | + return this.dispatch("type", typeGetter, codecGetter); |
63 | 72 | } |
64 | 73 |
|
65 | 74 | default Codec<T> withAlternative(Codec<? extends T> alternative) { |
66 | | - return Codecs.withAlternative(this, alternative); |
| 75 | + return this.either(alternative).xmap(Either::merge, Either::left); |
67 | 76 | } |
68 | 77 |
|
69 | 78 | default Codec<List<T>> listOf() { |
70 | | - return Codecs.listOf(this); |
| 79 | + return new ListCodec<>(this); |
71 | 80 | } |
72 | 81 |
|
73 | 82 | default Codec<List<T>> listOrSingle() { |
74 | | - return Codecs.listOrSingle(this); |
| 83 | + return this.listOf().withAlternative(this.flatComapMap( |
| 84 | + Collections::singletonList, |
| 85 | + list -> list.size() == 1 ? CodecResult.success(list.get(0)) : CodecResult.error("Not a singleton") |
| 86 | + )); |
75 | 87 | } |
76 | 88 |
|
77 | 89 | default Codec<Optional<T>> optional() { |
78 | | - return Codecs.optional(this); |
| 90 | + return new OptionalCodec<>(this); |
79 | 91 | } |
80 | 92 |
|
81 | 93 | default Codec<T> optional(Supplier<T> fallback) { |
82 | | - return Codecs.optional(this, fallback); |
| 94 | + return new DefaultedOptionalCodec<>(this, fallback); |
83 | 95 | } |
84 | 96 |
|
85 | 97 | default Codec<T> optional(T fallback) { |
86 | | - return Codecs.optional(this, fallback); |
| 98 | + return this.optional(() -> fallback); |
| 99 | + } |
| 100 | + |
| 101 | + default <B> Codec<Either<T, B>> either(Codec<B> other) { |
| 102 | + return new EitherCodec<>(this, other, false); |
87 | 103 | } |
88 | 104 |
|
89 | | - default <B> Codec<Either<T, B>> xor(Codec<B> otherCodec) { |
90 | | - return Codecs.xor(this, otherCodec); |
| 105 | + default <B> Codec<Either<T, B>> xor(Codec<B> other) { |
| 106 | + return new EitherCodec<>(this, other, true); |
91 | 107 | } |
92 | 108 |
|
93 | 109 | default MapCodec<T> fieldOf(String name) { |
94 | | - return Codecs.fieldOf(this, name); |
| 110 | + return new FieldOfCodec<>(this, name); |
| 111 | + } |
| 112 | + |
| 113 | + // factories |
| 114 | + |
| 115 | + static <T> Codec<T> of(Encoder<? super T> encoder, Decoder<T> decoder) { |
| 116 | + return new Codec<T>() { |
| 117 | + @Override |
| 118 | + public CodecResult<T> decode(JsonValue json) { |
| 119 | + return decoder.decode(json); |
| 120 | + } |
| 121 | + |
| 122 | + @Override |
| 123 | + public CodecResult<? extends JsonValue> encode(T value) { |
| 124 | + return encoder.encode(value); |
| 125 | + } |
| 126 | + }; |
| 127 | + } |
| 128 | + |
| 129 | + /** |
| 130 | + * Create a codec from a function that may throw a {@link JsonException}. |
| 131 | + */ |
| 132 | + static <T> Codec<T> throwing(Function<? super JsonValue, ? extends T> decoder, Function<? super T, ? extends JsonValue> encoder) { |
| 133 | + return new Codec<T>() { |
| 134 | + @Override |
| 135 | + public CodecResult<T> decode(JsonValue json) { |
| 136 | + try { |
| 137 | + return CodecResult.success(decoder.apply(json)); |
| 138 | + } catch (JsonException e) { |
| 139 | + return CodecResult.error(e.getMessage()); |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + @Override |
| 144 | + public CodecResult<? extends JsonValue> encode(T value) { |
| 145 | + return CodecResult.success(encoder.apply(value)); |
| 146 | + } |
| 147 | + }; |
| 148 | + } |
| 149 | + |
| 150 | + static <T extends Number> Codec<T> number(Function<Number, T> decoder) { |
| 151 | + return throwing( |
| 152 | + json -> decoder.apply(json.asNumber().strictValue()), |
| 153 | + number -> new JsonNumber(number.doubleValue()) |
| 154 | + ); |
| 155 | + } |
| 156 | + |
| 157 | + static <T> Codec<T> byName(Function<? super T, @Nullable String> nameGetter, Function<? super String, ? extends @Nullable T> byName) { |
| 158 | + return new ByNameCodec<>(nameGetter, byName); |
| 159 | + } |
| 160 | + |
| 161 | + static <T extends Enum<T>> Codec<T> byName(Class<T> clazz, Function<T, String> nameGetter) { |
| 162 | + T[] values = clazz.getEnumConstants(); |
| 163 | + Map<String, T> byName = new HashMap<>(); |
| 164 | + for (T value : values) { |
| 165 | + byName.put(nameGetter.apply(value), value); |
| 166 | + } |
| 167 | + return byName(nameGetter, byName::get); |
| 168 | + } |
| 169 | + |
| 170 | + static <T extends Enum<T>> Codec<T> byName(Class<T> clazz) { |
| 171 | + return byName(clazz, Enum::name); |
| 172 | + } |
| 173 | + |
| 174 | + static <T> Codec<T> unit(T unit) { |
| 175 | + return new UnitCodec<>(unit); |
| 176 | + } |
| 177 | + |
| 178 | + static <T> Codec<T> codecDispatch(Codec<MapCodec<? extends T>> codec, Function<? super T, ? extends MapCodec<? extends T>> getter) { |
| 179 | + return codec.dispatch(getter, Function.identity()); |
95 | 180 | } |
96 | 181 | } |
0 commit comments