FOUND SOLUTION TO THE PROBLEM For the people who will be stuck like me!:
in order to handle third party java or scala Objects for jackson deserialization, you can either use Mixins( but you need to reconfigure the jackson mapper or user Modules)
OR
you can simply create a class called MyClassDeserializer that extends JsonDeserializer
and use the #JsonDeserialize(using = MyClassDeserializer.class) annotation.
exemple :
it's really simple and works like a charm! :)
public class User implements Identity{
#JsonProperty("_id")
private String id;
#JsonDeserialize(using = OptionDeserializer.class)
public Option<String> email;
}
public class OptionDeserializer extends JsonDeserializer<Option> {
#Override
public Option deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
//code can be improved
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
return Option.apply(node.get("email").getText());
}
}
hello guys i'm having some bad time trying to deserialize this with jackson using Jongo
public class User implements Identity{
#JsonProperty("_id")
private String id;
public Option<String> email;
}
Option is an abstract type, I'm really new to this, is there a way to actually tell jackson how to translate it?
this User object is correctly saved to mongo, but cannot be read :/ i've spent so many hours trying to understand you guys are my last resort!
thanks (keeps looking)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can
not construct instance of scala.Option, problem: abstract types either
need to be mapped to concrete types, have custom deserializer, or be
instantiated with additional type information
at [Source: de.undercouch.bson4jackson.io.LittleEndianInputStream#6f255853; pos:
237] (through reference chain: models.User["email"])
posting popo joe's answer as an answer:
FOUND SOLUTION TO THE PROBLEM For the people who will be stuck like me!: in order to handle third party java or scala Objects for jackson deserialization, you can either use Mixins( but you need to reconfigure the jackson mapper or user Modules) OR you can simply create a class called MyClassDeserializer that extends JsonDeserializer and use the #JsonDeserialize(using = MyClassDeserializer.class) annotation.
example :
it's really simple and works like a charm! :)
public class User implements Identity{
#JsonProperty("_id")
private String id;
#JsonDeserialize(using = OptionDeserializer.class)
public Option<String> email;
}
public class OptionDeserializer extends JsonDeserializer<Option> {
#Override
public Option deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException,
JsonProcessingException {`
//code can be improved`
ObjectCodec oc = jsonParser.getCodec();
JsonNode node = oc.readTree(jsonParser);
return Option.apply(node.get("email").getText());
}
}
Related
I have a scenario where I have custom implemented deseralization to my class Item extends JsonDeserializer<User> which deserializes my object in one fashion.
While Kafka uses org.springframework.kafka.support.serializer.JsonDeserializer which is being overridden by this my new implementation.
How can I make my Kafka stop from using the custom implementation ?
Custom Implemented code
public class ItemDeserializer extends JsonDeserializer<Item> {
public ItemDeserializer() {
this(null);
}
public ItemDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Item deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int id = (Integer) ((IntNode) node.get("id")).numberValue();
String itemName = node.get("itemName").asText();
int userId = (Integer) ((IntNode) node.get("createdBy")).numberValue();
return new Item(id, itemName, new User(userId, null));
}}
And while using custom deserialiser, I call it like
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Item.class, new ItemDeserializer());
mapper.registerModule(module);
Item readValue = mapper.readValue(json, Item.class);
Item class is annotated with
#JsonDeserialize(using = ItemDeserializer.class)
public class Item {
public int id;
public String itemName;
public User owner;
}
Kafka Configurations
consumer:
deserializer:
key.delegate.class: com.apache.kafka.common.serialization.stringDeserializer
value.delegate.class: com.apache.kafka.common.serialization.JsonDeserializer
json:
trusted:
packages: com.package.to.item
Tried Searching for 3 days. This is my last resort on how to solve this problem
You're creating your own ObjectMapper. If you want to configure Spring-Kafka's ObjectMapper instance, then see here
make my Kafka stop from using the custom implementation
If you want to use the String Deserializer rather than JSON, then you don't need delegates. Simply set the deserializer directly
spring:
kafka:
consumer:
key-deserializer: com.apache.kafka.common.serialization.StringDeserializer
value-deserializer: ...
Beyond this, unclear why you'd actually want strings that you'd need to deserialize yourself rather than consuming actual Item classes
I would need to convert JSON to POJO, which implements following marker interface:
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import java.io.Serializable;
#JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class"
)
public interface MyInterface extends Serializable {
}
this is pojo:
public class MyPojo implements MyInterface
{
private static final long serialVersionUID = 1L;
private String phonetype;
private String cat;
//getters + setters
}
and this is snippet code where I have to use it, but I don't know what I have to do:
String json = "{\"phonetype\":\"N95\",\"cat\":\"WP\"}";
ObjectMapper mapper = new ObjectMapper();
MyPojo myPojo = mapper.readValue(json, MyPojo.class);//here I get exception: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class path.to.MyPojo]: missing type id property '#class'
// at [Source: (String)"{"phonetype":"N95","cat":"WP"}"; line: 1, column: 30]
When I don't implement interface into MyPojo, everything works correctly without exception, but I would need implement it.
I use library jackson-databind-2.9.8
I would like to asking you, if you have any suggestion how can I solve it or if does it exist another approach how parse Pojo implementing interface showed above. I mean something missing in my interface but I don't know what.
Thank you so much
You may try to use a custom deserializer as shown in the following link How arbitrary JSON string can be deserialized to java POJO?
I have a problem with deserialization json into POJO class which looks like this:
#Data
public class Foo {
private String fieldA;
private String fieldB;
private IBar fieldC;
}
IBar is an interface which defines getters for some classes. One of the solutions what I found is to use #JsonDeserialize(as = BarImpl.class) where BarImpl will implement IBar interface. Problem is classes which implement that interface (for instance BarImpl) are in another maven module where I don't have access from current module so I cannot use one of this impl classes in that annotation. Can you tell me if there is another solution?
Thank you in advice.
Are you sure you mean deserialization? You'll need a concrete implementation of your interface if Jackson's going to be able to create Java objects for you.
deserialization = Json String -> Java object
serialization = Java object -> Json String
When serializing Jackson will use the runtime class of the object, so it will use the actual implementations rather than attempt to use the interface. If you want to customize this you can add a serializer for your interface. You'll need to decide exactly what you want to write out.
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(IBar.class, new JsonSerializer<IBar>() {
#Override
public void serialize(IBar value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeStringField("fieldName", value.getFieldName());
gen.writeEndObject();
}
});
objectMapper.registerModule(module);
I'm looking to have multiple jackson deserializers for the same object(s) all based on a custom annotation.
Ideally I'd have a single POJO like:
public class UserInfo {
#Redacted
String ssn;
String name;
}
Under "normal" conditions I want this object to be serialized the default way:
{"ssn":"123-45-6789", "name":"Bob Smith"}
but for logging purposes (for example) I want to redact the SSN so it doesn't get saved in our logs:
{"ssn":"xxx-xx-xxxx", "name":"Bob Smith"}
I've also looked into using #JsonSerialize and come up with:
public class UserInfo {
#JsonSerialize(using = RedactedSerializer.class, as=String.class)
String firstName;
String lastName;
}
The problem with this is that it ALWAYS uses this rule. Can multiple #JsonSerializers be added and only the specified one be used within the runtime code?
I've also seen "views" but ideally I'd like to atleast show that the field was present on the request - even if I dont know the value.
The 100% safe way would be to use different DTO in different requests. But yeah, if you cant do that, use #JsonView and custom serializer, something like:
class Views {
public static class ShowSSN {}
}
private static class MyBean{
#JsonSerialize(using = MyBeanSerializer.class)
#JsonView(Views.ShowSSN.class)
String ssn;
//getter setter constructor
}
private class MyBeanSerializer extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator gen,
SerializerProvider serializers) throws IOException {
Class<?> jsonView = serializers.getActiveView();
if (jsonView == Views.ShowSSN.class)
gen.writeString(value); // your custom serialization code here
else
gen.writeString("xxx-xx-xxxx");
}
}
And use it like:
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
MyBean bean = new MyBean("123-45-6789");
System.out.println(mapper.writerWithView(Views.ShowSSN.class)
.writeValueAsString(bean));
// results in {"ssn":"123-45-6789"}
System.out.println(mapper.writeValueAsString(bean));
// results in {"ssn":"xxx-xx-xxxx"}
}
Also for example in spring it would be really easy to use
#Controller
public class MyController {
#GetMapping("/withView") // results in {"ssn":"123-45-6789"}
#JsonView(Views.ShowSSN.class)
public #ResponseBody MyBean withJsonView() {
return new MyBean("123-45-6789");
}
#GetMapping("/withoutView") // results in {"ssn":"xxx-xx-xxxx"}
public #ResponseBody MyBean withoutJsonView() {
return new MyBean("123-45-6789");
}
}
I think you could achieve that dynamically by coding not annotations,
inside your methods, you can set the proper Serializer and switch between them
(The code depends on your Jackson version)
ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null));
testModule.addSerializer(new RedactedSerializer()); // assuming serializer declares correct class to bind to
mapper.registerModule(testModule);
https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers
I have a a map that looks like this:
public class VerbResult {
#JsonProperty("similarVerbs")
private Map<Verb, List<Verb>> similarVerbs;
}
My verb class looks like this:
public class Verb extends Word {
#JsonCreator
public Verb(#JsonProperty("start") int start, #JsonProperty("length") int length,
#JsonProperty("type") String type, #JsonProperty("value") VerbInfo value) {
super(length, length, type, value);
}
//...
}
I want to serialize and deserialize instances of my VerbResult class, but when I do I get this error: Can not find a (Map) Key deserializer for type [simple type, class my.package.Verb]
I read online that you need to tell Jackson how to deserialize map keys, but I didn't find any information explaining how to go about doing this. The verb class needs to be serialized and deserialzed outside of the map as well, so any solution should preserve this functionality.
Thank you for your help.
After a day of searching, I came across a simpler way of doing it based on this question. The solution was to add the #JsonDeserialize(keyUsing = YourCustomDeserializer.class) annotation to the map. Then implement your custom deserializer by extending KeyDeserializer and override the deserializeKey method. The method will be called with the string key and you can use the string to build the real object, or even fetch an existing one from the database.
So first in the map declaration:
#JsonDeserialize(keyUsing = MyCustomDeserializer.class)
private Map<Verb, List<Verb>> similarVerbs;
Then create the deserializer that will be called with the string key.
public class MyCustomDeserializer extends KeyDeserializer {
#Override
public MyMapKey deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
//Use the string key here to return a real map key object
return mapKey;
}
}
Works with Jersey and Jackson 2.x
As mentioned above the trick is that you need a key deserializer (this caught me out as well). In my case a non-String map key was configured on my class but it wasn't in the JSON I was parsing so an extremely simple solution worked for me (simply returning null in the key deserializer).
public class ExampleClassKeyDeserializer extends KeyDeserializer
{
#Override
public Object deserializeKey( final String key,
final DeserializationContext ctxt )
throws IOException, JsonProcessingException
{
return null;
}
}
public class ExampleJacksonModule extends SimpleModule
{
public ExampleJacksonModule()
{
addKeyDeserializer(
ExampleClass.class,
new ExampleClassKeyDeserializer() );
}
}
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule( new ExampleJacksonModule() );
Building on the answer given here that suggests to implement a Module with a deserializer. The JodaTime Module is an easy to understand full example of a module containing serializers and deserializers.
Please note that the Module feature was introduced in Jackson version 1.7 so you might need to upgrade.
So step by step:
create a module containing a (de)serializer for your class based on the Joda example
register that module with mapper.registerModule(module);
and you'll be all set
Assuming we have a Map property, like the following:
class MyDTO{
#JsonSerialize(keyUsing = MyObjectKeySerializer.class)
#JsonDeserialize(keyUsing = MyObjectKeyDeserilazer.class)
private Map<MyObjectKey , List<?>> map;
}
We serilize the MyObjectKey as a json string, while call objectMapper.writeAsString;
And deserilize from the json string,to MyObjectKey
public class MyObjectKeySerializer extends StdSerializer<MyObjectKey> {
public Serializer() {
super(MyObjectKey.class);
}
#Override
public void serialize(MyObjectKey value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeFieldName(JsonUtil.toJSONString(value));
}
}
public class MyObjectKeyDeserializer extends KeyDeserializer {
#Override
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
return JsonUtil.toObject(key, MyObjectKey.class);
}
}
After scouring the web, I think I have a decent solution for how to handle POJO-style keys (although, as always, you are best served not using a full object as a map key).
Serializer (registered as a Jackson module, inside of Spring Boot):
#Bean
fun addKeySerializer(): Module =
SimpleModule().addKeySerializer(YourClass::class.java, YourClassSerializer())
class YourClassSerializer() : JsonSerializer<YourClass>() {
override fun serialize(value: DataElement, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeFieldName(jacksonObjectMapper().writeValueAsString(value))
}
}
(note that, in a standard Java environment, you will have to instantiate your own objectMapper instance here)
Deserializer:
#Bean
fun addKeyDeserializer(): Module =
SimpleModule().addKeyDeserializer(YourClass::class.java, YourClassDeserializer())
class YourClassDeserializer() : KeyDeserializer() {
override fun deserializeKey(key: String, ctxt: DeserializationContext): YourClass? {
return ctxt.parser.readValueAs(YourClass::class.java)
}
}