Skip to content

Instantly share code, notes, and snippets.

@Anton3
Last active August 25, 2022 07:37
Show Gist options
  • Save Anton3/348e639b6d46c3598f3311b9feca8578 to your computer and use it in GitHub Desktop.
Save Anton3/348e639b6d46c3598f3311b9feca8578 to your computer and use it in GitHub Desktop.
package name.anton3.vkapi.generator.json
import com.fasterxml.jackson.annotation.JsonUnwrapped
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.deser.ContextualDeserializer
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.databind.node.TreeTraversingParser
import com.fasterxml.jackson.databind.util.NameTransformer
class SinglePolyUnwrappedDeserializer<T : Any> : JsonDeserializer<T>(), ContextualDeserializer {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): T = error("Not implemented")
override fun createContextual(ctxt: DeserializationContext, property: BeanProperty?): JsonDeserializer<T> =
SinglePolyUnwrappedDeserializerImpl(ctxt)
}
private class SinglePolyUnwrappedDeserializerImpl<T : Any>(ctxt: DeserializationContext) :
StdDeserializer<T>(null as JavaType?) {
private val type: JavaType = ctxt.contextualType
private val beanDeserializer: JsonDeserializer<T>
private val ownPropertyNames: Set<String>
private val unwrappedType: JavaType
private val unwrappedPropertyName: String
private val nameTransformer: NameTransformer
init {
val description: BeanDescription = ctxt.config.introspect(type)
var tempUnwrappedAnnotation: JsonUnwrapped? = null
val unwrappedProperties = description.findProperties().filter { prop ->
listOfNotNull(prop.constructorParameter, prop.mutator, prop.field).any { member ->
val unwrappedAnnotation: JsonUnwrapped? = member.getAnnotation(JsonUnwrapped::class.java)
if (unwrappedAnnotation != null) {
tempUnwrappedAnnotation = unwrappedAnnotation
member.allAnnotations.add(notUnwrappedAnnotation)
}
unwrappedAnnotation != null
}
}
val unwrappedProperty = when (unwrappedProperties.size) {
0 -> error("@JsonUnwrapped properties not found in ${type.typeName}")
1 -> unwrappedProperties.single()
else -> error("Multiple @JsonUnwrapped properties found in ${type.typeName}")
}
nameTransformer = tempUnwrappedAnnotation!!.run { NameTransformer.simpleTransformer(prefix, suffix) }
unwrappedPropertyName = unwrappedProperty.name
ownPropertyNames = description.findProperties().mapTo(mutableSetOf()) { it.name }
ownPropertyNames.remove(unwrappedPropertyName)
ownPropertyNames.removeAll(description.ignoredPropertyNames)
unwrappedType = unwrappedProperty.primaryType
val rawBeanDeserializer = ctxt.factory.createBeanDeserializer(ctxt, type, description)
(rawBeanDeserializer as? ResolvableDeserializer)?.resolve(ctxt)
@Suppress("UNCHECKED_CAST")
beanDeserializer = rawBeanDeserializer as JsonDeserializer<T>
}
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): T {
val node = p.readValueAsTree<ObjectNode>()
val ownNode = ObjectNode(ctxt.nodeFactory)
val unwrappedNode = ObjectNode(ctxt.nodeFactory)
node.fields().forEach { (key, value) ->
val transformed: String? = nameTransformer.reverse(key)
if (transformed != null && key !in ownPropertyNames) {
unwrappedNode.replace(transformed, value)
} else {
ownNode.replace(key, value)
}
}
ownNode.replace(unwrappedPropertyName, unwrappedNode)
val syntheticParser = TreeTraversingParser(ownNode)
syntheticParser.nextToken()
return beanDeserializer.deserialize(syntheticParser, ctxt)
}
private class NotUnwrapped(
@Suppress("unused")
@field:JsonUnwrapped(enabled = false)
@JvmField
val dummy: Nothing
)
companion object {
val notUnwrappedAnnotation: JsonUnwrapped =
NotUnwrapped::class.java.getField("dummy").getAnnotation(JsonUnwrapped::class.java)
}
}
@hydrogen2
Copy link

hydrogen2 commented Sep 13, 2021

import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.ResolvableDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.util.NameTransformer;
import com.fasterxml.jackson.databind.node.TreeTraversingParser;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**

public class SinglePolyUnwrappedDeserializer extends JsonDeserializer implements ContextualDeserializer {

@Override
public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
    throw new UnsupportedOperationException("not implemented");
}

private static class CancelUnwrapped {
    @JsonUnwrapped(enabled = false)
    public Object dummy;
}

private static JsonUnwrapped cancelUnwrappedAnnotation;

static {
    try {
        cancelUnwrappedAnnotation = CancelUnwrapped.class.getField("dummy").getAnnotation(JsonUnwrapped.class);
    } catch (NoSuchFieldException e) {
    }
}

private static class Impl<T> extends StdDeserializer{
    private JsonDeserializer<T> beanDeserializer;
    private Set<String> ownPropertyNames;
    private String unwrappedPropertyName;
    private NameTransformer nameTransformer;

    public Impl(DeserializationContext deserializationContext) throws JsonMappingException {
        super((JavaType) null);

        JavaType type = deserializationContext.getContextualType();

        BeanDescription description = deserializationContext.getConfig().introspect(type);

        final JsonUnwrapped[] tempUnwrappedAnnotation = {null};

        List<BeanPropertyDefinition> unwrappedProperties = description.findProperties().stream().filter(prop ->
                Arrays.asList(prop.getConstructorParameter(), prop.getMutator(), prop.getField()).stream()
                        .filter(Objects::nonNull)
                        .anyMatch(member -> {
                            JsonUnwrapped unwrappedAnnotation = member.getAnnotation(JsonUnwrapped.class);
                            if (unwrappedAnnotation != null) {
                                tempUnwrappedAnnotation[0] = unwrappedAnnotation;
                                member.getAllAnnotations().add(cancelUnwrappedAnnotation);
                            }
                            return unwrappedAnnotation != null;
                        })).collect(Collectors.toList());

        if (unwrappedProperties.size() == 0) {
            throw new UnsupportedOperationException("@JsonUnwrapped properties not found in ${type.typeName}");
        } else if (unwrappedProperties.size() > 1) {
            throw new UnsupportedOperationException("Multiple @JsonUnwrapped properties found in ${type.typeName}");
        }

        BeanPropertyDefinition unwrappedProperty = unwrappedProperties.get(0);

        nameTransformer = NameTransformer.simpleTransformer(tempUnwrappedAnnotation[0].prefix(), tempUnwrappedAnnotation[0].suffix());

        unwrappedPropertyName = unwrappedProperty.getName();

        ownPropertyNames = description.findProperties().stream().map(p -> p.getName()).collect(Collectors.toSet());
        ownPropertyNames.remove(unwrappedPropertyName);
        ownPropertyNames.removeAll(description.getIgnoredPropertyNames());

        JsonDeserializer<Object> rawBeanDeserializer = deserializationContext.getFactory().createBeanDeserializer(deserializationContext, type, description);
        ((ResolvableDeserializer) rawBeanDeserializer).resolve(deserializationContext);
        beanDeserializer = (JsonDeserializer<T>) rawBeanDeserializer;
    }

    @Override
    public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        ObjectNode node = jsonParser.readValueAsTree();

        ObjectNode ownNode = deserializationContext.getNodeFactory().objectNode();
        ObjectNode unwrappedNode = deserializationContext.getNodeFactory().objectNode();

        node.fields().forEachRemaining(entry -> {
            String key = entry.getKey();
            JsonNode value = entry.getValue();

            String transformed = nameTransformer.reverse(key);

            if (transformed != null && !ownPropertyNames.contains(key)) {
                unwrappedNode.replace(transformed, value);
            } else {
                ownNode.replace(key, value);
            }
        });

        ownNode.replace(unwrappedPropertyName, unwrappedNode);

        TreeTraversingParser syntheticParser = new TreeTraversingParser(ownNode);
        syntheticParser.nextToken();
        return beanDeserializer.deserialize(syntheticParser, deserializationContext);
    }
}

@Override
public JsonDeserializer<?> createContextual(DeserializationContext deserializationContext, BeanProperty beanProperty) throws JsonMappingException {
    return new Impl<>(deserializationContext);
}

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment