All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.activej.json.JsonCodecs Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2020 ActiveJ LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.activej.json;

import com.dslplatform.json.BoolConverter;
import com.dslplatform.json.JsonReader;
import com.dslplatform.json.JsonWriter;
import com.dslplatform.json.NumberConverter;
import io.activej.common.annotation.StaticFactories;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;

import static io.activej.common.Checks.checkArgument;
import static io.activej.common.Checks.checkNotNull;
import static io.activej.common.Utils.newLinkedHashMap;
import static io.activej.common.Utils.transformIterator;
import static io.activej.json.JsonKeyCodec.ofStringKey;
import static io.activej.json.JsonValidationUtils.validateNotNull;

@SuppressWarnings({"ConstantConditions", "unchecked"})
@StaticFactories(JsonCodec.class)
public class JsonCodecs {

	public static JsonCodec ofString() {
		return new JsonCodecString();
	}

	public static JsonCodec ofByte() {
		return new JsonCodecByte();
	}

	public static JsonCodec ofShort() {
		return new JsonCodecShort();
	}

	public static JsonCodec ofInteger() {
		return new JsonCodecInteger();
	}

	public static JsonCodec ofLong() {
		return new JsonCodecLong();
	}

	public static JsonCodec ofFloat() {
		return new JsonCodecFloat();
	}

	public static JsonCodec ofDouble() {
		return new JsonCodecDouble();
	}

	public static JsonCodec ofBoolean() {
		return new JsonCodecBoolean();
	}

	public static JsonCodec ofCharacter() {
		return new JsonCodecCharacter();
	}

	public static JsonCodec ofLocalDate() {
		return new JsonCodecLocalDate();
	}

	private static final class JsonCodecString implements JsonCodec {
		@Override
		public String read(JsonReader reader) throws IOException {
			return validateNotNull(reader.readString());
		}

		@Override
		public void write(JsonWriter writer, String value) {
			writer.writeString(checkNotNull(value));
		}
	}

	private static final class JsonCodecByte implements JsonCodec {
		@Override
		public Byte read(JsonReader reader) throws IOException {
			int result = NumberConverter.deserializeInt(reader);
			if (result < 0 || result > 255) {
				throw reader.newParseError("Read an int not in range [0, 255] while trying to read a byte");
			}
			return (byte) result;
		}

		@Override
		public void write(JsonWriter writer, Byte value) {
			NumberConverter.serialize(checkNotNull(value) & 0xFF, writer);
		}
	}

	private static final class JsonCodecShort implements JsonCodec {
		@Override
		public Short read(JsonReader reader) throws IOException {
			return NumberConverter.deserializeShort(reader);
		}

		@Override
		public void write(JsonWriter writer, Short value) {
			NumberConverter.serialize(checkNotNull(value), writer);
		}
	}

	private static final class JsonCodecInteger implements JsonCodec {
		@Override
		public Integer read(JsonReader reader) throws IOException {
			return NumberConverter.deserializeInt(reader);
		}

		@Override
		public void write(JsonWriter writer, Integer value) {
			NumberConverter.serialize(checkNotNull(value), writer);
		}
	}

	private static final class JsonCodecLong implements JsonCodec {
		@Override
		public Long read(JsonReader reader) throws IOException {
			return NumberConverter.deserializeLong(reader);
		}

		@Override
		public void write(JsonWriter writer, Long value) {
			NumberConverter.serialize(checkNotNull(value), writer);
		}
	}

	private static final class JsonCodecFloat implements JsonCodec {
		@Override
		public Float read(JsonReader reader) throws IOException {
			return NumberConverter.deserializeFloat(reader);
		}

		@Override
		public void write(JsonWriter writer, Float value) {
			NumberConverter.serialize(checkNotNull(value), writer);
		}
	}

	private static final class JsonCodecDouble implements JsonCodec {
		@Override
		public Double read(JsonReader reader) throws IOException {
			return NumberConverter.deserializeDouble(reader);
		}

		@Override
		public void write(JsonWriter writer, Double value) {
			NumberConverter.serialize(checkNotNull(value), writer);
		}
	}

	private static final class JsonCodecBoolean implements JsonCodec {
		@Override
		public Boolean read(JsonReader reader) throws IOException {
			return BoolConverter.deserialize(reader);
		}

		@Override
		public void write(JsonWriter writer, Boolean value) {
			BoolConverter.serialize(checkNotNull(value), writer);
		}
	}

	private static final class JsonCodecCharacter implements JsonCodec {
		@Override
		public Character read(JsonReader reader) throws IOException {
			String string = reader.readString();
			if (string.length() != 1) {
				throw reader.newParseError("Read a string with length != 1 while trying to read a character");
			}
			return string.charAt(0);
		}

		@Override
		public void write(JsonWriter writer, Character value) {
			writer.writeString(checkNotNull(value).toString());
		}
	}

	private static final class JsonCodecLocalDate implements JsonCodec {
		@Override
		public LocalDate read(JsonReader reader) throws IOException {
			try {
				return LocalDate.parse(validateNotNull(reader.readString()));
			} catch (DateTimeParseException e) {
				throw reader.newParseError(e.getMessage());
			}
		}

		@Override
		public void write(JsonWriter writer, LocalDate value) {
			writer.writeString(checkNotNull(value).toString());
		}
	}

	public static > JsonCodec ofEnum(Class enumClass) {
		return new JsonCodec<>() {
			@Override
			public E read(JsonReader reader) throws IOException {
				try {
					return Enum.valueOf(enumClass, reader.readString());
				} catch (IllegalArgumentException e) {
					throw reader.newParseError(e.getMessage());
				}
			}

			@Override
			public void write(JsonWriter writer, E value) {
				writer.writeString(checkNotNull(value).name());
			}
		};
	}

	static JsonCodec> ofMapObject(Map> codecs) {
		return new AbstractMapJsonCodec, LinkedHashMap, Object>() {
			@Override
			protected Iterator> iterate(Map item) {
				checkArgument(item.size() == codecs.size());
				return item instanceof LinkedHashMap || !(codecs instanceof LinkedHashMap) ?
					transformIterator(item.entrySet().iterator(), entry -> (JsonMapEntry) JsonMapEntry.of(entry)) :
					transformIterator(codecs.keySet().iterator(), key -> {
						checkArgument(item.containsKey(key));
						return new JsonMapEntry<>(key, item.get(key));
					});
			}

			@Override
			protected @Nullable JsonEncoder encoder(String key, int index, Map item, Object value) {
				return (JsonEncoder) codecs.get(key);
			}

			@Override
			protected @Nullable JsonDecoder decoder(String key, int index, LinkedHashMap accumulator) throws JsonValidationException {
				JsonCodec codec = codecs.get(key);
				if (codec == null) throw new JsonValidationException("Key not found: " + key);
				return (JsonDecoder) codec;
			}

			@Override
			protected LinkedHashMap accumulator() {
				return newLinkedHashMap(codecs.size());
			}

			@Override
			protected void accumulate(LinkedHashMap accumulator, String key, int index, Object value) throws JsonValidationException {
				if (index >= codecs.size()) throw new JsonValidationException();
				accumulator.put(key, value);
			}

			@Override
			protected Map result(LinkedHashMap accumulator, int count) throws JsonValidationException {
				if (count != codecs.size()) throw new JsonValidationException();
				return accumulator;
			}
		};
	}

	public static  JsonCodec> ofMap(Function> codecsFn) {
		return new AbstractMapJsonCodec, LinkedHashMap, V>() {
			@Override
			protected Iterator> iterate(Map item) {
				return transformIterator(item.entrySet().iterator(), JsonMapEntry::of);
			}

			@Override
			protected @Nullable JsonEncoder encoder(String key, int index, Map item, V value) {
				return codecsFn.apply(key);
			}

			@Override
			protected @Nullable JsonDecoder decoder(String key, int index, LinkedHashMap accumulator) throws JsonValidationException {
				JsonCodec codec = codecsFn.apply(key);
				if (codec == null) throw new JsonValidationException("Key not found: " + key);
				return codec;
			}

			@Override
			protected LinkedHashMap accumulator() {
				return new LinkedHashMap<>();
			}

			@Override
			protected void accumulate(LinkedHashMap accumulator, String key, int index, V value) {
				accumulator.put(key, value);
			}

			@Override
			protected Map result(LinkedHashMap accumulator, int count) {
				return accumulator;
			}
		};
	}

	public static  JsonCodec ofArray(JsonCodec codec, Supplier supplier) {
		return new AbstractArrayJsonCodec() {
			@Override
			protected Iterator iterate(T[] item) {
				return Arrays.asList(item).iterator();
			}

			@Override
			protected @Nullable JsonEncoder encoder(int index, T[] item, T value) {
				return codec;
			}

			@Override
			protected @Nullable JsonDecoder decoder(int index, T[] accumulator) {
				return codec;
			}

			@Override
			protected T[] accumulator() {
				return supplier.get();
			}

			@Override
			protected void accumulate(T[] accumulator, int index, T value) throws JsonValidationException {
				if (index >= accumulator.length) throw new JsonValidationException();
				accumulator[index] = value;
			}

			@Override
			protected T[] result(T[] accumulator, int count) throws JsonValidationException {
				if (accumulator.length != count) throw new JsonValidationException();
				return accumulator;
			}
		};
	}

	public static  JsonCodec> ofList(JsonCodec codec) {
		return new AbstractArrayJsonCodec, ArrayList, T>() {
			@Override
			protected Iterator iterate(List item) {
				return item.iterator();
			}

			@Override
			protected @Nullable JsonEncoder encoder(int index, List item, T value) {
				return codec;
			}

			@Override
			protected @Nullable JsonDecoder decoder(int index, ArrayList accumulator) {
				return codec;
			}

			@Override
			protected ArrayList accumulator() {
				return new ArrayList<>();
			}

			@Override
			protected void accumulate(ArrayList accumulator, int index, T value) {
				accumulator.add(value);
			}

			@Override
			protected List result(ArrayList accumulator, int count) {
				return accumulator;
			}
		};
	}

	public static  JsonCodec> ofSet(JsonCodec codec) {
		return new AbstractArrayJsonCodec, LinkedHashSet, T>() {
			@Override
			protected Iterator iterate(Set item) {
				return item.iterator();
			}

			@Override
			protected @Nullable JsonEncoder encoder(int index, Set item, T value) {
				return codec;
			}

			@Override
			protected @Nullable JsonDecoder decoder(int index, LinkedHashSet accumulator) {
				return codec;
			}

			@Override
			protected LinkedHashSet accumulator() {
				return new LinkedHashSet<>();
			}

			@Override
			protected void accumulate(LinkedHashSet accumulator, int index, T value) throws JsonValidationException {
				if (accumulator.contains(value)) throw new JsonValidationException();
				accumulator.add(value);
			}

			@Override
			protected Set result(LinkedHashSet accumulator, int count) {
				return accumulator;
			}
		};
	}

	public static JsonCodec ofArrayObject(JsonCodec... codecs) {
		return new AbstractArrayJsonCodec() {
			@Override
			protected Iterator iterate(Object[] item) {
				checkArgument(item.length == codecs.length);
				return Arrays.asList(item).iterator();
			}

			@Override
			protected @Nullable JsonEncoder encoder(int index, Object[] item, Object value) {
				return (JsonEncoder) codecs[index];
			}

			@Override
			protected @Nullable JsonDecoder decoder(int index, Object[] accumulator) throws JsonValidationException {
				if (index >= accumulator.length) throw new JsonValidationException();
				return (JsonDecoder) codecs[index];
			}

			@Override
			protected Object[] accumulator() {
				return new Object[codecs.length];
			}

			@Override
			protected void accumulate(Object[] accumulator, int index, Object value) {
				accumulator[index] = value;
			}

			@Override
			protected Object[] result(Object[] accumulator, int count) throws JsonValidationException {
				if (count != codecs.length) throw new JsonValidationException();
				return accumulator;
			}
		};
	}

	public static  JsonCodec> ofMap(JsonCodec codec) {
		return ofMap(ofStringKey(), codec);
	}

	public static  JsonCodec> ofMap(JsonKeyCodec keyCodec, JsonCodec codec) {
		return new AbstractMapJsonCodec, LinkedHashMap, V>() {
			@Override
			protected Iterator> iterate(Map item) {
				return transformIterator(item.entrySet().iterator(), entry -> JsonMapEntry.of(entry, keyCodec));
			}

			@Override
			protected @Nullable JsonEncoder encoder(String key, int index, Map item, V value) {
				return codec;
			}

			@Override
			protected @Nullable JsonDecoder decoder(String key, int index, LinkedHashMap accumulator) {
				return codec;
			}

			@Override
			protected LinkedHashMap accumulator() {
				return new LinkedHashMap<>();
			}

			@Override
			protected void accumulate(LinkedHashMap accumulator, String key, int index, V value) throws JsonValidationException {
				accumulator.put(keyCodec.decode(key), value);
			}

			@Override
			protected Map result(LinkedHashMap accumulator, int count) {
				return accumulator;
			}
		};
	}

	public static  JsonCodec<@Nullable T> ofNullable(JsonCodec codec) {
		return new NullableJsonCodec<>(codec);
	}

	public static  JsonCodec ofObject(JsonConstructor1 constructor,
		String field1, Function getter1, JsonCodec codec1
	) {
		//noinspection unchecked,rawtypes
		return ObjectJsonCodec.builder(params ->
				(T) ((JsonConstructor1) constructor).create(params[0]))
			.with(field1, getter1, codec1)
			.build();
	}

	public static  JsonCodec ofObject(JsonConstructor2 constructor,
		String field1, Function getter1, JsonCodec codec1,
		String field2, Function getter2, JsonCodec codec2
	) {
		//noinspection unchecked,rawtypes
		return ObjectJsonCodec.builder(params ->
				(T) ((JsonConstructor2) constructor).create(params[0], params[1]))
			.with(field1, getter1, codec1)
			.with(field2, getter2, codec2)
			.build();
	}

	public static  JsonCodec ofObject(JsonConstructor3 constructor,
		String field1, Function getter1, JsonCodec codec1,
		String field2, Function getter2, JsonCodec codec2,
		String field3, Function getter3, JsonCodec codec3
	) {
		//noinspection unchecked,rawtypes
		return ObjectJsonCodec.builder(params ->
				(T) ((JsonConstructor3) constructor).create(params[0], params[1], params[2]))
			.with(field1, getter1, codec1)
			.with(field2, getter2, codec2)
			.with(field3, getter3, codec3)
			.build();
	}

	public static  JsonCodec ofObject(JsonConstructor4 constructor,
		String field1, Function getter1, JsonCodec codec1,
		String field2, Function getter2, JsonCodec codec2,
		String field3, Function getter3, JsonCodec codec3,
		String field4, Function getter4, JsonCodec codec4
	) {
		//noinspection unchecked,rawtypes
		return ObjectJsonCodec.builder(params ->
				(T) ((JsonConstructor4) constructor).create(params[0], params[1], params[2], params[3]))
			.with(field1, getter1, codec1)
			.with(field2, getter2, codec2)
			.with(field3, getter3, codec3)
			.with(field4, getter4, codec4)
			.build();
	}

	public static  JsonCodec ofObject(JsonConstructor5 constructor,
		String field1, Function getter1, JsonCodec codec1,
		String field2, Function getter2, JsonCodec codec2,
		String field3, Function getter3, JsonCodec codec3,
		String field4, Function getter4, JsonCodec codec4,
		String field5, Function getter5, JsonCodec codec5
	) {
		//noinspection unchecked,rawtypes
		return ObjectJsonCodec.builder(params ->
				(T) ((JsonConstructor5) constructor).create(params[0], params[1], params[2], params[3], params[4]))
			.with(field1, getter1, codec1)
			.with(field2, getter2, codec2)
			.with(field3, getter3, codec3)
			.with(field4, getter4, codec4)
			.with(field5, getter5, codec5)
			.build();
	}

	public static  JsonCodec ofObject(JsonConstructor6 constructor,
		String field1, Function getter1, JsonCodec codec1,
		String field2, Function getter2, JsonCodec codec2,
		String field3, Function getter3, JsonCodec codec3,
		String field4, Function getter4, JsonCodec codec4,
		String field5, Function getter5, JsonCodec codec5,
		String field6, Function getter6, JsonCodec codec6
	) {
		//noinspection unchecked,rawtypes
		return ObjectJsonCodec.builder(params ->
				(T) ((JsonConstructor6) constructor).create(params[0], params[1], params[2], params[3], params[4], params[5]))
			.with(field1, getter1, codec1)
			.with(field2, getter2, codec2)
			.with(field3, getter3, codec3)
			.with(field4, getter4, codec4)
			.with(field5, getter5, codec5)
			.with(field6, getter6, codec6)
			.build();
	}

	public static  JsonCodec transform(JsonCodec codec, Function to, JsonFunction from) {
		return new JsonCodec<>() {
			@Override
			public R read(JsonReader reader) throws IOException {
				return from.apply(codec.read(reader));
			}

			@Override
			public void write(JsonWriter writer, R value) {
				codec.write(writer, to.apply(value));
			}
		};
	}

	public static class NullableJsonCodec implements JsonCodec {
		private final JsonCodec codec;

		public NullableJsonCodec(JsonCodec codec) {this.codec = codec;}

		@Override
		public @Nullable T read(JsonReader reader) throws IOException {
			if (reader.wasNull()) return null;
			return codec.read(reader);
		}

		@Override
		public void write(JsonWriter writer, @Nullable T value) {
			if (value == null) writer.writeNull();
			else codec.write(writer, value);
		}
	}
}