2

I have a class with many many fields, all of which are Optional when deserialized from JSON.

Is there a way to convert this to a class with matching all non-Optional types.

My optional class

@JsonDeserialize public class VeryOptionalClass { @Value.Parameter @JsonProperty("fieldOne") public Optional<String> fieldOne() @Value.Parameter @JsonProperty("fieldTwo") public Optional<Double> fieldTwo() @Value.Parameter @JsonProperty("fieldOne") public Optional<String> fieldThree() @Value.Parameter @JsonProperty("fieldThree") public Optional<SomeObject> fieldFour() ---skip a few lines here--- @Value.Parameter @JsonProperty("fieldTwentyfive") public Optional<String> fieldHundredTwentyFive() } 

My desired result:

public class TotallyNonOptionalClass { public String fieldOne() public Double fieldTwo() public String fieldThree() public SomeObject fieldFour() ---skip a few lines here--- public Integer fieldHundredTwentyFive() } 

If all the Optional fields are populated, is this possible to do elegantly? I know I can do a builder like:

VeryOptionalClass myOptionalClass = someProvider(someInput) TotallyNonOptionalClass.builder() .fieldOne(myOptionalClass.fieldOne().get()) .fieldTwo(myOptionalClass.fieldTwo().get()) .fieldThree(myOptionalClass.fieldThree().get()) .fieldFour(myOptionalClass.fieldFour().get()) ---skip a few lines here--- .fieldHundredTwentyFive(myOptionalClass.fieldHundredTwentyFive().get()) .build() 

Or if I wanted I could use Optional.orElse(someDefault) and list out all the fields in the builder. But let's say I know that all the optionals are non-empty, is there a way to achieve the same thing without a very long Builder?

PS. My class doesn't actually have 125 fields, but it is long and I'd rather not have to make changes in both VeryOptionalClass and TotallyNonOptionalClass every time I add a field/function.

1
  • Since it looks like you're using Jackson, take a look at ObjectMapper.convertValue(). Commented Dec 20, 2024 at 1:52

1 Answer 1

2

Background

ModelMapper is a library built specifically for mapping structurally similar heterogeneous objects onto each other. In other words, two different types of objects that have similarly named and typed fields. Thus, it naturally lends itself to your problem.

Unfortunately, it does not have support for Java 8's Optional wrappers built-in.

Thankfully, ModelMapper does allow you to specify custom converters.

Credit

Please read more about ModelMapper: https://modelmapper.org/

My code below is loosely based on: https://stackoverflow.com/a/29060055/2045291

Code

Custom Converter for Optional<T> --> T

Note: you may want to verify the type of the Optional's contents matches the destination type.

import org.modelmapper.spi.*; import java.util.Optional; public class OptionalExtractingConverter implements ConditionalConverter<Optional, Object> { @Override public MatchResult match(Class<?> aClass, Class<?> aClass1) { if (Optional.class.isAssignableFrom(aClass) && !Optional.class.isAssignableFrom(aClass1)) { return MatchResult.FULL; } return MatchResult.NONE; } @Override public Object convert(MappingContext<Optional, Object> context) { final Optional<?> source = context.getSource(); if (source != null && source.isPresent()) { final MappingContext<?, ?> childContext = context.create(source.get(), context.getDestinationType()); return context.getMappingEngine().map(childContext); } return null; } } 

Test

import org.modelmapper.ModelMapper; import java.util.Optional; public class MappingService { private static final ModelMapper modelMapper = new ModelMapper(); static { modelMapper.typeMap(OptionalObject.class, NonOptionalObject.class) .setPropertyConverter(new OptionalExtractingConverter()); } public static void main(String[] args) { OptionalObject optionalObject = new OptionalObject(Optional.of("test")); NonOptionalObject nonOptionalObject = modelMapper.map(optionalObject, NonOptionalObject.class); System.out.println("⭐️ RESULT: " + nonOptionalObject.getName()); } } 

Source Object (w/ Optional field)

import java.util.Optional; public class OptionalObject { private Optional<String> name; public OptionalObject() { } public OptionalObject(final Optional<String> name) { this.name = name; } public Optional<String> getName() { return name; } public void setName(Optional<String> name) { this.name = name; } } 

Destination Object (w/ non-Optional field)

public class NonOptionalObject { private String name; public NonOptionalObject() { } public NonOptionalObject(final String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.