Last active
January 25, 2026 11:31
-
-
Save Garciat/f19938419e53d72d42ce79c839ab9f3d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.uber.money.caribbean.paymentprofilev2.spring; | |
| import com.fasterxml.jackson.core.JsonProcessingException; | |
| import com.fasterxml.jackson.databind.JsonNode; | |
| import com.fasterxml.jackson.databind.ObjectMapper; | |
| import com.fasterxml.jackson.databind.node.ArrayNode; | |
| import com.fasterxml.jackson.databind.node.BooleanNode; | |
| import com.fasterxml.jackson.databind.node.DoubleNode; | |
| import com.fasterxml.jackson.databind.node.JsonNodeFactory; | |
| import com.fasterxml.jackson.databind.node.LongNode; | |
| import com.fasterxml.jackson.databind.node.NullNode; | |
| import com.fasterxml.jackson.databind.node.ObjectNode; | |
| import com.fasterxml.jackson.databind.node.TextNode; | |
| import com.uber.money.caribbean.paymentprofilev2.entities.index.PaymentProfileIndexingConfig; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.HashMap; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Optional; | |
| import java.util.function.BiFunction; | |
| import java.util.function.Function; | |
| import java.util.function.Supplier; | |
| import java.util.stream.Collectors; | |
| import java.util.stream.Stream; | |
| import lombok.RequiredArgsConstructor; | |
| import org.springframework.boot.context.properties.ConfigurationProperties; | |
| import org.springframework.context.annotation.Bean; | |
| import org.springframework.context.annotation.Configuration; | |
| @Configuration | |
| public class IndexingModule { | |
| @ConfigurationProperties(prefix = "indexing") | |
| @Bean | |
| public Map<String, Object> indexingRawStructure() { | |
| return new HashMap<>(); | |
| } | |
| @Bean | |
| public PaymentProfileIndexingConfig indexingConfig(Map<String, Object> indexingRawStructure) | |
| throws JsonProcessingException { | |
| Object root = indexingRawStructure.get("indexingRawStructure"); | |
| var mapper = new ObjectMapper(); | |
| var parser = new SpringPropertiesToJson<>(new JacksonJsonConstructor(JsonNodeFactory.instance)); | |
| var tree = parser.parseSpringProperties(root); | |
| return mapper.treeToValue(tree, PaymentProfileIndexingConfig.class); | |
| } | |
| interface JsonConstructor<Element> { | |
| Element makeNull(); | |
| Element makeString(String value); | |
| Element makeNumber(Long value); | |
| Element makeNumber(Double value); | |
| Element makeBoolean(boolean value); | |
| Element makeArray(List<Element> value); | |
| Element makeObject(Map<String, Element> value); | |
| } | |
| @RequiredArgsConstructor | |
| public static class JacksonJsonConstructor implements JsonConstructor<JsonNode> { | |
| private final JsonNodeFactory nodeFactory; | |
| @Override | |
| public JsonNode makeNull() { | |
| return NullNode.getInstance(); | |
| } | |
| @Override | |
| public JsonNode makeString(String value) { | |
| return new TextNode(value); | |
| } | |
| @Override | |
| public JsonNode makeNumber(Long value) { | |
| return new LongNode(value); | |
| } | |
| @Override | |
| public JsonNode makeNumber(Double value) { | |
| return new DoubleNode(value); | |
| } | |
| @Override | |
| public JsonNode makeBoolean(boolean value) { | |
| return BooleanNode.valueOf(value); | |
| } | |
| @Override | |
| public JsonNode makeArray(List<JsonNode> value) { | |
| return new ArrayNode(nodeFactory, value); | |
| } | |
| @Override | |
| public JsonNode makeObject(Map<String, JsonNode> value) { | |
| return new ObjectNode(nodeFactory, value); | |
| } | |
| } | |
| @RequiredArgsConstructor | |
| public static class SpringPropertiesToJson<Element> { | |
| private final JsonConstructor<Element> json; | |
| public Element parseSpringProperties(Object root) { | |
| return parse(root); | |
| } | |
| // Recursion entry-point | |
| private Element parse(Object node) { | |
| return fullParser() | |
| .apply(node) | |
| .orElseThrow(() -> new IllegalStateException("Could not parse: " + node.getClass())); | |
| } | |
| private Partial<Object, Element> fullParser() { | |
| return asValue() | |
| .flatMap( | |
| nullParser() | |
| .orElse(booleanParser()) | |
| .orElse(longParser()) | |
| .orElse(doubleParser()) | |
| .orElse(stringParser())) | |
| .orElse(asContainer().flatMap(arrayParser().orElse(objectParser()))); | |
| } | |
| private Partial<Object, String> asValue() { | |
| return Partial.tryCast(String.class); | |
| } | |
| private Partial<Object, Map<String, Object>> asContainer() { | |
| return Partial.tryCast(Map.class).map(m -> (Map<String, Object>) m); | |
| } | |
| private Partial<String, Element> nullParser() { | |
| return Partial.match("null").replaceLazy(json::makeNull); | |
| } | |
| private Partial<String, Element> stringParser() { | |
| return Partial.of(json::makeString); | |
| } | |
| private Partial<String, Element> longParser() { | |
| return Partial.trying((String s) -> Long.parseLong(s), NumberFormatException.class) | |
| .map(json::makeNumber); | |
| } | |
| private Partial<String, Element> doubleParser() { | |
| return Partial.trying(Double::parseDouble, NumberFormatException.class) | |
| .map(json::makeNumber); | |
| } | |
| private Partial<String, Element> booleanParser() { | |
| return Partial.match("true").replace(true) | |
| .orElse(Partial.match("false").replace(false)) | |
| .map(json::makeBoolean); | |
| } | |
| private Partial<Map<String, Object>, Element> arrayParser() { | |
| return Partial.of(this::readAsList).map(this::recurse).map(json::makeArray); | |
| } | |
| private Partial<Map<String, Object>, Element> objectParser() { | |
| return Partial.of(this::readAsMap).map(this::recurse).map(json::makeObject); | |
| } | |
| private List<Element> recurse(List<Object> items) { | |
| return items.stream().map(this::parse).collect(Collectors.toList()); | |
| } | |
| private Map<String, Element> recurse(Map<String, Object> items) { | |
| return items.entrySet().stream().collect(Collectors.toMap(k -> k.getKey(), v -> parse(v.getValue()))); | |
| } | |
| // Tries to parse shape: {"0": x, "1": y, "2": z, ...} | |
| private Optional<List<Object>> readAsList(Map<String, Object> map) { | |
| var output = new ArrayList<>(); | |
| for (int i = 0; i < map.size(); i++) { | |
| var item = map.get(Integer.toString(i)); | |
| if (item == null) { | |
| return Optional.empty(); | |
| } else { | |
| output.add(item); | |
| } | |
| } | |
| return Optional.of(output); | |
| } | |
| // Tries to parse shape: {"0": x, "1": y, "2": z, ...} | |
| private Optional<List<Object>> readAsListFunctional(Map<String, Object> map) { | |
| return Stream.iterate(0, n -> n + 1) | |
| .limit(map.size()) | |
| .map(i -> Partial.lookup(Integer.toString(i)).map(Arrays::asList)) | |
| .reduce((top, next) -> Partial.join(top, next, ListUnion::apply)) | |
| .orElseGet(Partial::fail) | |
| .apply(map); | |
| } | |
| private Optional<Map<String, Object>> readAsMap(Map<String, Object> map) { | |
| return Optional.of(map); | |
| } | |
| } | |
| interface Partial<T, R> extends Function<T, Optional<R>> { | |
| Optional<R> apply(T input); | |
| default <S> Partial<T, S> map(Function<R, S> f) { | |
| return input -> apply(input).map(f); | |
| } | |
| default <S> Partial<T, S> replace(S value) { | |
| return map(ignored -> value); | |
| } | |
| default <S> Partial<T, S> replaceLazy(Supplier<S> value) { | |
| return map(ignored -> value.get()); | |
| } | |
| default <S> Partial<T, S> flatMap(Partial<R, S> next) { | |
| return input -> apply(input).flatMap(next); | |
| } | |
| default Partial<T, R> orElse(Partial<T, R> fallback) { | |
| return input -> { | |
| var first = apply(input); | |
| if (first.isPresent()) { | |
| return first; | |
| } else { | |
| return fallback.apply(input); | |
| } | |
| }; | |
| } | |
| static <T, R> Partial<T, R> of(Partial<T, R> it) { | |
| return it; | |
| } | |
| static <T, R> Partial<T, R> of(Function<T, R> it) { | |
| return input -> Optional.ofNullable(it.apply(input)); | |
| } | |
| static <R> Partial<Object, R> tryCast(Class<R> target) { | |
| return trying(target::cast, ClassCastException.class); | |
| } | |
| static <T, R, E extends Throwable> Partial<T, R> trying(FunctionEx<T, R, E> f, Class<E> expected) { | |
| return input -> { | |
| try { | |
| return Optional.of(f.apply(input)); | |
| } catch (Throwable ex){ | |
| if (expected.isInstance(ex)) { | |
| return Optional.empty(); | |
| } else { | |
| throw new RuntimeException("Unexpected exception type", ex); | |
| } | |
| } | |
| }; | |
| } | |
| static <T> Partial<T, T> match(T expected) { | |
| return input -> { | |
| if (expected.equals(input)) { | |
| return Optional.of(input); | |
| } else { | |
| return Optional.empty(); | |
| } | |
| }; | |
| } | |
| static <T, R> Partial<Map<T, R>, R> lookup(T key) { | |
| return input -> Optional.ofNullable(input.get(key)); | |
| } | |
| static <T, R, S> Partial<T, S> join(Partial<T, R> left, Partial<T, R> right, BiFunction<R, R, S> f) { | |
| return input -> left.apply(input).flatMap(r1 -> right.apply(input).map(r2 -> f.apply(r1, r2))); | |
| } | |
| static <T> Partial<T, T> identity() { | |
| return Optional::of; | |
| } | |
| static <T, R> Partial<T, R> fail() { | |
| return input -> Optional.empty(); | |
| } | |
| } | |
| interface FunctionEx<T, R, E extends Throwable> { | |
| R apply(T input) throws E; | |
| } | |
| public static class ListUnion { | |
| public static <T> List<T> apply(List<T> xs, List<T> ys) { | |
| return Stream.concat(xs.stream(), ys.stream()).collect(Collectors.toList()); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment