Using Jackson Mixins to selectively ignore only provided properties - java

I have the following situation:
public class A {
private String someProperty;
private String anotherProperty;
public A() {}
// getter/setter
An ObjectMapper configuration as follows (enabled by default, but worth noting to get the point of the question across):
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
and an input JSON that looks as follows:
{
"someProperty": "someValue",
"anotherProperty":"anotherValue",
"unwantedProperty":"unwantedValue"
}
When deserializing this JSON using objectMapper.readValue(bytes, A.class), as expected, it gives me an exception:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "unwantedProperty"
Now, what I want to do, is the following:
Only deserialize properties that are explicitly provided by A.class
Use a MixIn to ignore all unwanted, known properties. So for example, I know that unwantedProperty is part of the JSON, but I don't need it, so I want to ignore it.
Still raise an UnrecognizedPropertyException if a new, unknown property suddenly appears in my JSON.
The reason that I wish to use something like a MixIn class for this is that in reality, the input JSON has several dozens of fields. I would prefer not to clutter my A.class with dozens and dozens of unused properties with #JsonIgnore on them, so that it only contains the fields that I really want. If a new property unexpectedly does come along, I want to be forced to have a look at it.
I thought that I could allow this behaviour by using a MixIn as follows:
public abstract class AMixIn {
#JsonIgnore private String unwantedProperty;
together with:
objectMapper.addMixIn(A.class, AMixIn.class);
but this seemingly has no effect. I've also tried creating getters in AMixIn and giving those #JsonIgnore, but this also has no result.
Am I using MixIns incorrectly here? Is what I'm trying to do even possible (as described in the 3 points above)? Or, is there a better way to do this?
I've tried searching, but my use case is a bit esoteric, so I haven't had much luck.

Answer to question 1:
You can instruct Jackson to ignore unknown properties.
I tend to configure the ObjectMapper to ignore them,
here is some sample code:
private ObjectMapper mapper;
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
// This matches the Fuse Mapper configuration.
builder.featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
mapper = builder.build();
You can also annotate the class to ignore unknown properties.
Here is some sample code:
#jsonignoreproperties(ignoreunknown = true)
public class A
{
...
Note about question 2:
In order for the use-mixin-to-ignore-fields-in-json strategy to work,
the fields to be ignored must exist in the class.
In your case,
this means that class A must have a field "unwantedProperty" for the mixin to work correctly.
Direction to solve questions 2 and 3
You cannot use the MixIn feature of Jackson to solve either
question 2 or question 3.
Instead,
you will need to write a custom deserializer.
I suggest that you also use a custom Jackson annotation
that configures a list of ignored-unrecognized-fields
and have your custom deserializer only throw the exception
for unrecognized fields that are not part of the
ignored-unrecognized-fields list.

Well, shows how well I can search; as always, Jackson provides some way of doing whatever needs doing. In case it might help someone else:
There exists the #JsonIgnoreProperties annotation which, in addition to the ignoreUnknown property (that I already knew), supports a list of properties (via value()) to ignore during de-/serialization (which I did not know).
This is not quite the same as the intended solution above, but having these properties inside of #JsonIgnoreProperties({ ... }) in the class header instead of the class body is a good enough compromise for me.
So, the solution would be:
#JsonIgnoreProperties({"unwantedProperty"})
public class A {
// same as above...
}
Still, if there is a MixIn solution which can completely decouple these things, I'd still like to see it. I'll accept my own answer if nothing comes up in a few days.

Related

JSON Binding #JsonbTypeDeserializer annotation ignored on enums?

I'm converting a JAXB application to JSON-B and I've run into an issue while trying to deserialize a Java enum using a custom JsonbDeserializer inside one of my tests.
The original JSON I need to deserialize contains ints referencing the enum's constants. Therefore my custom JsonbDeserializer needs to take the int and return the enum constant with the matching ordinal. It looks like this:
#JsonbTypeDeserializer(Region.RegionDeserializer.class)
public enum Region implements BaseEnum {
REGION_A,
REGION_B;
static final class RegionDeserializer implements JsonbDeserializer<Region> {
// deserialize() method returns REGION_A for 0 and REGION_B for 1.
}
}
Then I run it like this:
try (var jsonb = JsonbBuilder.create()) {
var result = jsonb.fromJson(text, Region.class);
} catch (final Exception ex) {
fail(ex);
}
Unfortunately, here's what I get back:
java.lang.IllegalArgumentException: No enum constant Region.1
at java.base/java.lang.Enum.valueOf(Enum.java:266)
at org.eclipse.yasson.internal.serializer.EnumTypeDeserializer.deserialize(EnumTypeDeserializer.java:40)
As you can see, RegionDeserializer is not used. Instead, the default enum deserializer is used. Looking into the JSON-B docs, I see I should register the deserializer manually:
JsonbConfig config = new JsonbConfig()
.withDeserializer(RegionDeserializer.class);
Jsonb jsonb = JsonbBuilder.create(config);
...
And when I do that, the code in fact works. But here's my question - what can I do to have the JsonbTypeDeserializer annotation registered automatically? Considering I have a lot of enums I need custom deserializers for, registering them manually really doesn't scale.
EDIT 1: I have tried to use #JsonbCreator-annotated static method instead, and the result was the same. The default enum deserializer was still used.
The JSON-B specification mentions both ways of registering the custom deserializer:
There are two ways how to register JsonbSerializer/JsonbDeserializer:
Using JsonbConfig::withSerializers/JsonbConfig::withDeserializers method;
Annotating a type with JsonbSerializer/JsonbDeserializer annotation.
The fact that the annotation does not work is a bug. I could reproduce this on Yasson 1.0.6, but not on Yasson 2.0.0-M1. Perhaps updating to the latest version solves your problem?

Java Webclient: How to serialize to Pascal JSON?

I am trying to interface with another system that is has extremely specific integration parameters. They don't have any code written to ignore case sensitivity and, long story short, for a post request I am trying to make, they are expecting a JSON body with field names in Pascal case instead of Camel Case and the request fails without Pascal. We are using WebClient to send integration calls so we can support reactive flows in our code. As far as I've been able to tell, when I use WebClient to serialize to JSON, the request is being converted to use Camel Case, which I would normally want.
How can I serialize this to Pascal instead? Everything I try to research about this ends up landing me in .NET land, but I'm not writing this in C#. I'm writing it in Java.
//For example:
{"originTypeCode":"US","camelCaseFieldName":"FAILED"} // FAILURE
{"OriginTypeCode":"US","PascalFieldName":"SUCCESS"} // SUCCESS
I have two ideas:
1) This seems less ideal, but perhaps more intuitive. The idea is to convert the object I'm trying to post to JSON first, then with a parser convert all the fields from Camel Case to Pascal, then try and post that with my WebClient method. This doesn't seem like the most ideal way to do this. I'd imagine there is probably something a lot cleaner.
2) The second idea is that my WebClient instance serializes using a Jackson serializer. I think if I were to create a new Bean of WebClient/Jackson ObjectMapper, maybe I can write a custom converter to use specifically for this integration flow. This seems like it is perhaps cleaner, but digging through WebClient and it's build methods, it's difficult to figure out how to accomplish this. Below I'm posting the beans as I have them currently defined. Digging into this kind of thing is pretty new to me, so I'm not sure what would need to be changed or where. The WebClient bean is from a WebClientConfig class and the ObjectMapper is from my JacksonConfig class.
#Bean
public WebClient webClient() {
return WebClient.builder().clientConnector(getClientHttpConnector()).build();
}
#Bean
#Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return objectMapper;
}
I am definitely open to other suggestions as well.
The comments from #GriffeyDog have helped me figure out what I need to do. For anyone that stumbles across this, the solution was the following:
If you want certain classes to serialize in specific ways, you can annotate the class itself with the annotation #JsonNaming, and then specify a naming strategy, a list of which can be found here: https://java-focus.com/jackson-property-naming-strategy/. For my use case, I used PropertyNamingStrategy.UpperCamelCaseStrategy.
#JsonNaming(value = PropertyNamingStrategy.UpperCamelCaseStrategy.class)
public class MyPascalSerializedClass {}
//All fields in this class will serialize to "UpperCamelCase" instead of "normalCamelCase".
Additionally, if you wanted to specify certain fields, you can use the #JsonProperty annotation to override even the class annotation. For example, I had a field within my class that had to map to a JSON format that didn't fit to any standard convention, so I was able to use this.
#JsonProperty("ULDNumber")
private String uldNumber
//This field will serialize to the specified "ULDNumber".
This is all part of the com.fasterxml.jackson library. For further documentation you can refer to the link above and the following: https://fasterxml.github.io/jackson-databind/javadoc/2.7/com/fasterxml/jackson/databind/PropertyNamingStrategy.UpperCamelCaseStrategy.html

Make Jackson Subtypes extensible without editing the Supertypes java-file

In my company we have a fixed JSON message structure:
{
"headerVal1": ""
"headerVal2": ""
"customPayload": {
"payloadType":""
}
}
I would like to have some kind of library, which allows me, to not care for the company defined message structure, and instead just send and receive the payload.
My idea was, to define the structure of the company template as one object, and use subtypes of a PayloadObject.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.MINIMAL_CLASS,
property = "payloadType",
visible = false)
public abstract class PayloadObject {
}
Now I can create subclasses of the PayloadObject, and it can be automatically deserialized in this structure, as long as the property payloadType has a string ".SubTypeName".
This is problematic, since I cannot customize it, not even remove the superflous . in the beginning. This is unfortunately not necessarily compatible with other, existing systems in the company, we need to interface with.
The alternative is, to add a #JsonSubTypes-annotation in which I can add all the possible subtypes - which I don't want to know when writing the library. So this option won't work for me.
I thought, it might help to have the #JsonType-annoation with the subtypes, but I still have to add the #JsonSubTypes, which does not help.
Is there a way, to add subtypes to a basetype without modifying the basetypes java-file?
If this helps: We are working with Java Spring.
ObjectMapper has a method registerSubtypes(NamedType) which can be used to add subtypes for use, without having them in the annotations.
For this I created a new Annotation (I might have reused #JsonTypeName, but it might be abusive)
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface MyJsonSubtype
{
public String jsonTypeName();
}
Then I wrote me a method
public static void registerMyJsonSubtypes(ObjectMapper om, Object... reflectionArgs) {
Reflections reflections = new Reflections(reflectionArgs);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(MyJsonSubtype.class);
for (Class type : types) {
String name = ((MyJsonSubtype) type.getAnnotation(MyJsonSubtype.class)).jsonTypeName();
om.registerSubtypes(new NamedType(type, name));
}
}
which uses Reflections to get all annotated types declared inside searched packages and registers them as subtypes for the ObjectMapper.
This still requires the #JsonTypeInfo-annotation on the base class to mark the object as potentially extensible, so the mapper knows, which property to use, to resolve the name, but I figure, this is is providable.
My main attention was on the problem, that I don't want to declare all future subtypes in an annotation on the base class.
I am a Java beginner though, so please share your thoughts, if this is unnecessary or could/should/must be improved.

Jackson: ignore the #JsonIgnoreProperties annotation

I have a class that I serialize and I use the #JsonIgnoreProperties at the class level to exclude some fields from it.
Lately I have an use case where I need those fields serialized.
Is there a way to make a writer/reader that ignores the annotation?
I was looking into #JsonView but it seems #JsonIgnoreProperties takes precedence over it.
#JsonFilter could help you in this case.
By defining custom json filter, Jackson will dynamically resolve filter given class uses, dynamically, allowing per-call reconfiguration of filtering.
You can find detailed explanation and usage example here
Some usefull information about dynamic ignoral you find here
I have come up with a solution, don't know if it the best one but gets the job done ...
I ended up using #JsonView.
So I have 2 views like this:
public class Views {
public static class Public { }
public static class Extended extends Public { }
}
and the default Spring mapper configured as
mapper.setConfig(mapper.getSerializationConfig().withView(Views.Public.class));
mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
and the class looks like
public class Foo{
String name;
#JsonView(Views.Extended.class)
String title;
...}
By setting up a default view on the object mapper it causes it to ignore all the other not specified views. The fields with no annotation will always be serialized, as per config.
Then, when I need the whole class to be serialized I use:
objectMapper.writer().withView(Views.Extended.class).writeValueAsString(value);

How does XStream select its converters?

The only documentation on XStream converters that I can find is on these two pages:
Tutorial
List of all shipped converters
When XStream is parsing XML input, it uses a ConverterLookup (and by default, a DefaultConverterLookup) to lookup which converter to use by class. I'd like to configure my XStream mapper to use my own custom ConverterLookup, but only see a getConverterLookup() method, not a respective setter.
I have an instance where XStream is encountering a Date value in the XML, and returning using the respective DateConverter. I want it to use a different converter, which (I believe) means I need to set/register my own Converter impl. Just can't figure out how to do this. Thanks in advance.
First of all your question is in fact two unrelated questions, I'll try my best to answer them both.
Converters
To your second question regarding date conversion. Which in my mind seems to be the reason why you are here.
The basic way of adding your own converter is rather simple, the method registerConverter should give your a clue. If you are wondering how to implement a Converter I suggest you take a look at one of the many converters already provided by XStream. On an extra note I feel like I must mention the priority of converters.
The converters can be registered with an explicit priority. By
default they are registered with XStream.PRIORITY_NORMAL. Converters
of same priority will be used in the reverse sequence they have been
registered. The default converter, i.e. the converter which will be
used if no other registered converter is suitable, can be registered
with priority XStream.PRIORITY_VERY_LOW. XStream uses by default the
ReflectionConverter as the fallback converter.
In other terms, given two converters accepting the same classes, the one who was added last will be used.
ConverterLookup
To answer how you can use your ConverterLookup there are two ways which may yield the same results, personally I would go for alternative 2.
1) Overriding getConverterLookup
XStream xs = new XStream(){
#Override
public ConverterLookup getConverterLookup() {
return new ConverterLookup() {
public Converter lookupConverterForType(Class type) {
//Do your magic here
}
};
}
};
2) Using a Mapper
In this case I would keep the DefaultMapper and instead implement MapperWrapper's for my new mappings. (Have a look at buildMapper inside of XStream.java to see some of the defaults) Initialize like this:
ClassLoader classLoader = new ClassLoaderReference(new CompositeClassLoader());
Mapper mapper = new DefaultMapper(classLoader);
mapper = new MyOwnMapperWrapper(mapper);
XStream xs = new XStream(null, mapper, new XppDriver());

Categories

Resources