package core.framework.impl.json; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.cfg.MapperConfig; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; import com.fasterxml.jackson.databind.type.MapType; import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.databind.util.ISO8601DateFormat; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.module.afterburner.AfterburnerModule; import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector; import java.io.IOException; import java.io.UncheckedIOException; import java.lang.reflect.Type; import java.util.Map; import java.util.Optional; /** * used internally, performance is top priority in design * * @author neo */ public final class JSONMapper { public static final ObjectMapper OBJECT_MAPPER = createObjectMapper(); private static ObjectMapper createObjectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new Jdk8Module()); mapper.registerModule(new JavaTimeModule()); mapper.registerModule(new AfterburnerModule()); mapper.setDateFormat(new ISO8601DateFormat()); mapper.setAnnotationIntrospector(new JAXBAnnotationIntrospector()); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.configure(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME, true); return mapper; } public static <T> T fromMapValue(Type instanceType, Map<String, String> map) { ObjectMapper objectMapper = OBJECT_MAPPER; JavaType type = objectMapper.getTypeFactory().constructType(instanceType); try { byte[] json = objectMapper.writeValueAsBytes(map); return objectMapper.readValue(json, type); } catch (IOException e) { throw new UncheckedIOException(e); } } public static Map<String, String> toMapValue(Object instance) { ObjectMapper objectMapper = OBJECT_MAPPER; MapType type = objectMapper.getTypeFactory().constructMapType(Map.class, String.class, String.class); try { byte[] json = objectMapper.writeValueAsBytes(instance); return objectMapper.readValue(json, type); } catch (IOException e) { throw new UncheckedIOException(e); } } public static <T> T fromJSON(Type instanceType, byte[] json) { return fromJSON(instanceType, json, 0, json.length); } // jackson detects encoding and default to utf-8, works with our scenario, so there not to specify charset public static <T> T fromJSON(Type instanceType, byte[] json, int offset, int length) { JavaType type = OBJECT_MAPPER.getTypeFactory().constructType(instanceType); try { return OBJECT_MAPPER.readValue(json, offset, length, type); } catch (IOException e) { throw new UncheckedIOException(e); } } public static byte[] toJSON(Object instance) { try { return OBJECT_MAPPER.writeValueAsBytes(instance); } catch (IOException e) { throw new UncheckedIOException(e); } } private static class JAXBAnnotationIntrospector extends JaxbAnnotationIntrospector { private static final long serialVersionUID = 9089203444578006521L; JAXBAnnotationIntrospector() { super(TypeFactory.defaultInstance()); } @Override public TypeResolverBuilder<?> findPropertyContentTypeResolver(MapperConfig<?> config, AnnotatedMember am, JavaType containerType) { if (!containerType.hasRawClass(Optional.class) && !containerType.isContainerType()) { throw new IllegalArgumentException("expect container type (got " + containerType + ")"); } return _typeResolverFromXmlElements(am); } } }