package restx.tests.json; import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; import com.fasterxml.jackson.databind.deser.ContextualDeserializer; import com.fasterxml.jackson.databind.deser.ResolvableDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.type.ArrayType; import com.fasterxml.jackson.databind.type.MapType; import com.google.common.base.Optional; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; /** * Date: 3/2/14 * Time: 21:35 */ public class JsonWithLocationsParser { public ParsedJsonWithLocations parse(File file, Charset cs, Class type) throws IOException { return parse(new FileJsonSource(file, cs), type); } public ParsedJsonWithLocations parse(URL url, Charset cs, Class type) throws IOException { return parse(new URLJsonSource(url, cs), type); } public ParsedJsonWithLocations parse(String content, Class type) throws IOException { return parse(new StringJsonSource("", content), type); } public ParsedJsonWithLocations parse(JsonSource source, Class type) throws IOException { String content = source.content(); ParsedJsonLocations locations = new ParsedJsonLocations(content); Object o = reader(locations, type).readValue(content); return new ParsedJsonWithLocations(locations, o); } protected ObjectReader reader(final ParsedJsonLocations locations, Class type) { SimpleModule module = new SimpleModule().setDeserializerModifier(new BeanDeserializerModifier() { @Override public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) { return new ContextualLocationDeserializerWrapper(locations, super.modifyDeserializer(config, beanDesc, deserializer)); } @Override public JsonDeserializer<?> modifyArrayDeserializer(DeserializationConfig config, ArrayType valueType, BeanDescription beanDesc, JsonDeserializer<?> deserializer) { return new ContextualLocationDeserializerWrapper(locations, super.modifyArrayDeserializer(config, valueType, beanDesc, deserializer)); } @Override public JsonDeserializer<?> modifyMapDeserializer(DeserializationConfig config, MapType type, BeanDescription beanDesc, JsonDeserializer<?> deserializer) { return new ContextualLocationDeserializerWrapper(locations, super.modifyMapDeserializer(config, type, beanDesc, deserializer)); } }); return new ObjectMapper().registerModule(module).reader(type); } public static class ParsedJsonLocations { private final String source; private Map<Object, JsonObjectLocation> locations = new HashMap<>(); public ParsedJsonLocations(String source) { this.source = source; } private void addLocation(Object o, JsonLocation from, JsonLocation to) { locations.put(objectKey(o), new JsonObjectLocation(source, from, to)); } protected String objectKey(Object o) { return System.identityHashCode(o) + "-" + o; } public Optional<JsonObjectLocation> getLocationOf(Object o) { return Optional.fromNullable(locations.get(objectKey(o))); } @Override public String toString() { return "ParsedJsonLocations{" + "locations=" + locations + '}'; } } public static class ParsedJsonWithLocations { private final ParsedJsonLocations locations; private final Object root; public ParsedJsonWithLocations(ParsedJsonLocations locations, Object root) { this.locations = locations; this.root = root; } public ParsedJsonLocations getLocations() { return locations; } public Object getRoot() { return root; } @Override public String toString() { return "ParsedJsonWithLocations{" + "locations=" + locations + ", root=" + root + '}'; } } private static class ContextualLocationDeserializerWrapper extends LocationDeserializerWrapper implements ContextualDeserializer, ResolvableDeserializer { private final ContextualDeserializer contextualDeserializer; private ContextualLocationDeserializerWrapper(ParsedJsonLocations locations, JsonDeserializer deserializer) { super(locations, deserializer); if (deserializer instanceof ContextualDeserializer) { contextualDeserializer = (ContextualDeserializer) deserializer; } else { contextualDeserializer = null; } } @Override public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException { if (contextualDeserializer != null) { return new LocationDeserializerWrapper(locations, contextualDeserializer.createContextual(ctxt, property)); } return this; } } private static class LocationDeserializerWrapper extends JsonDeserializer implements ResolvableDeserializer { protected final ParsedJsonLocations locations; private final JsonDeserializer deserializer; private final ResolvableDeserializer resolvableDeserializer; private LocationDeserializerWrapper(ParsedJsonLocations locations, JsonDeserializer deserializer) { this.locations = locations; this.deserializer = deserializer; if (deserializer instanceof ResolvableDeserializer) { resolvableDeserializer = (ResolvableDeserializer) deserializer; } else { resolvableDeserializer = null; } } @Override public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonLocation currentLocation = jp.getCurrentLocation(); Object o = deserializer.deserialize(jp, ctxt); locations.addLocation(o, currentLocation, jp.getCurrentLocation()); return o; } @Override public void resolve(DeserializationContext ctxt) throws JsonMappingException { if (resolvableDeserializer != null) { resolvableDeserializer.resolve(ctxt); } } } }