Jackson use custom JSON deserializer with default - java

All code below is simplified version.
I have JSON structure:
{
"content" : {
"elements" : [ {
"type" : "simple"
},
{
"type" : "complex",
"content" : {
"elements" : [ {
"type" : "simple"
},
{
"type" : "simple"
},
{
"type" : "complex",
"content" : {
---- /// ----
}
} ]
}
} ]
}
}
I use Jackson lib for deserialization, and i am trying to implement a kind of "mix" custom with default deserializers.
I want Element object creates using custom ElementDeserializer but for Content field inside use default. Unfortunately things like that:
#JsonDeserialize(using = StdDeserializer.class)
#JsonProperty
Content content;
isn't work =(
Here is my code now:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Content {
#JsonProperty("elements")
ArrayList<Element> mElements;
}
#JsonDeserialize(using = ElementDeserializer.class)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Element<T extends ElementType> {
#JsonProperty
Content content;
T mField;
public Element(T field) {
mField = field;
}
}
public class ElementDeserializer extends JsonDeserializer<Element> {
#Override
public Element deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
Element element = null;
JsonNode node = jp.getCodec().readTree(jp);
if ("simple".equals(node.get("type").textValue())) {
element = new Element(new SimpleField());
} else if ("complex".equals(node.get("type").textValue())) {
element = new Element(new ComplexField());
}
return element;
}
}
I will be grateful for some help!

Not sure whether it is mandatory for you to use a custom deserializer (for reasons not indicated in your post). If it is not, then you can do without one, using the default deserializers.
Here is how:
#JsonIgnoreProperties(ignoreUnknown = true)
public class TopObject {
#JsonProperty
public Content content;
public TopObject() {
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Content {
#JsonProperty
public Element elements [];
public Content() {
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#Type(value = SimpleElement.class, name = "simple"),
#Type(value = ComplexElement.class, name = "complex")
})
public class Element {
public Element() {
}
}
public class SimpleElement extends Element {
public SimpleElement() {
}
}
public class ComplexElement extends Element {
#JsonProperty
public Content content;
public ComplexElement() {
}
}
Then unserialize the json data as a TopObject.class

Related

Issue with deserialisation of map of an abstract JAVA class

I'm trying to send a HTTP request to an API which accepts a JSON request body in this format
{
"firstName" : "XYZ",
"family" : {
"commonDetails" : {
"secondName" : "ABC"
},
"1" : "Mother name",
"2" : "Father name",
"3" : "Spouse name"
}
}
So I have defined a request payload the below way.
public class UserDetails {
private String firstName;
private Map<String, AbstractFamilyDetails> details;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
#JsonIgnoreProperties("type")
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type"
)
#JsonSubTypes({
#JsonSubTypes.Type(value = FamilyCommonDetails.class, name = "COMMON_DETAILS"),
#JsonSubTypes.Type(value = FamilyIndividual.class, name = "FAMILY")
})
public abstract class AbstractFamilyDetails {
private String type;
}
#Data
#EqualsAndHashCode(callSuper = true)
public class FamilyCommonDetails extends AbstractFamilyDetails {
private String secondName;
#Builder
public FamilyCommonDetails(String secondName) {
super("COMMON_DETAILS");
this.secondName = secondName;
}
public FamilyCommonDetails() {
super("COMMON_DETAILS");
}
#Override
public String toString() {
return this.secondName;
}
}
#Data
#EqualsAndHashCode(callSuper = true)
public class FamilyIndividual extends AbstractFamilyDetails {
private String individual;
#Builder
public FamilyIndividual(String individual) {
super("FAMILY");
this.individual = individual;
}
public FamilyIndividual() {
super("FAMILY");
}
#Override
public String toString() {
return this.individual;
}
}
But when I debug what json payload I'm hitting the server with turns out to be
{
"firstName" : "XYZ",
"family" : {
"commonDetails" : {
"secondName" : "ABC",
},
"1" : {"individual": "Mother name"},
"2" : {"individual": "Father name"},
"3" : {"individual": "Spouse name"}
}
}
Where is it that I'm going wrong? Do I have to define a custom jackson deserialiser to achieve this?
Try the #JsonUnwrapped annotation on the individual field.
See: https://fasterxml.github.io/jackson-annotations/javadoc/2.8/com/fasterxml/jackson/annotation/JsonUnwrapped.html
Have tried using #JsonUnwrapped as #user3296624 suggested. However it didn't work. There is an open issue on jackson-databind related to the same. Issue
Have tried #JsonAnySetter too, which did't help.
So, modified my request model to private Map<String, Object> details and discarded AbstractFamilyDetails as the last resort and used it accordingly. Not an ideal solution. But worked.

Deserialize json to java object using jackson. Design Patterns

json:
{
"events": [
{
"child_id": "unknown",
"some_info": "text",
"data": {
<some string>: {
"id": 1,
"prop": "propValue"
}
}
},
{
"child_id": "known",
"some_info": "text1",
"data": {
"id": 2,
"prop": "propValue1"
}
]
}
i am using structure like this:
public inteface EventService {
void handle();
}
public class EventsDto {
private List<Event> events;
//getter, setter, constructors
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
property = "child_id")
#JsonSubTypes({
#JsonSubTypes.Type(value = UnknownEvent.class, name = "unknown"),
#JsonSubTypes.Type(value = KnownEvent.class, name = "known")
})
public static abstract class Event implements EventService {
#JsonProperty("some_info")
private String someInfo;
//getter, setter, constructors
}
}
public class UnknownEvent extends EventsDto.Event {
private JsonNode data;
//getter, setter, constructors
#Override
public void handle() {
//something is happening
}
}
public class KnownEvent extends EventsDto.Event {
private Data data;
//getter, setter, constructors
#Override
public void handle() {
//something is happening
}
public static class Data {
private Integer id;
private String prop;
//getters, setters, constructors
}
}
public class Main {
public static void main(String[] args) {
String json = "<json>";
EventsDto eventsDto = objectMapper.readValue(json, EventsDto.class);
eventsDto.getEvents().forEach(event -> event.handle());
}
}
Is it possible to do something with UnknownEvent, to deserialize into a java object with all fields like KnownEvent without using JsonNode?
some string in json is dynamic and cannot be known in advance.
How can I pull the implementation of the "handle" method from the DTO into a separate class? If possible - do not change the logic of the main class.
I would like to hear advice on how to improve the code.

Jackson #JsonValue is conflicting with #JsonTypeInfo; How to make them work together

I am using the Jackson for serialization of a POJO. This POJO class consists of some fields and a Map<String,Object> others. I am using the Custom Serializer while writing JSON for this MAP field. I want to avoid getting the Map field name "others"ยด in my JSON. Hence, I am using the #JsonValueon theMapfield but using the#JsonValueis conflicting with#JsonTypeInfo`. I need both annotations in my class how can I achieve this?
As of now, I am getting the JSON as following: (With both #JsonValue and #JsonTypeInfo)
[ "Customer", {
"name" : "Rise Against",
"google:sub" : "MyValue-1",
"age" : "2000"
} ]
I would like to get the JSON as following with both #JsonValue and #JsonTypeInfo: (As you can see the others key is committed but its values are added to the JSON directly)
{
"isA" : "Customer",
"name" : "Batman",
"google:sub" : "MyValue-1",
"age" : "2008"
}
I am able to get the output but I need to remove the annotation from my class:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
But if I remove this then I will not get the isA property in my JSON. I want to know how to make the Jackson Json Serializer work both with the #JsonTypeInfo and #JsonValue.
Output without #JsonTypeInfo but with #JsonValue:
{
"name" : "Rise Against",
"google:sub" : "MyValue-1",
"age" : "2000"
}
Output without #JsonValue but with #JsonTypeInfo
{
"isA" : "Customer",
"name" : "",
"age" : "",
"others" : {
"name" : "Rise Against",
"google:sub" : "MyValue-1",
"age" : "2000"
}
}
Following is my Customer class Pojo:
#XmlRootElement(name = "Customer")
#XmlType(name = "Customer", propOrder = {"name", "age", "others"})
#XmlAccessorType(XmlAccessType.FIELD)
#NoArgsConstructor
#Getter
#Setter
#JsonInclude(JsonInclude.Include.NON_NULL)
#AllArgsConstructor
#JsonIgnoreType
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.PROPERTY, visible = true, property = "isA")
public class Customer {
#XmlElement(name = "name", required = true)
private String name;
#XmlElement(name = "age", required = true)
private String age;
#JsonSerialize(using = CustomExtensionsSerializer.class)
#XmlJavaTypeAdapter(TestAdapter.class)
#XmlPath(".")
#JsonValue
private Map<String, Object> others = new HashMap<>();
#JsonAnySetter
public void setOthers(String key, Object value) {
others.put(key, value);
}
public Map<String, Object> getOthers() {
return others;
}
}
Following is my `Custom serializer:
public class CustomExtensionsSerializer extends JsonSerializer<Map<String, Object>> {
private static final ObjectMapper mapper = new ObjectMapper();
#Override
public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
recusiveSerializer(value, gen, serializers);
gen.writeEndObject();
}
public void recusiveSerializer(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
for (Map.Entry<String, Object> extension : value.entrySet()) {
if (extension.getValue() instanceof Map) {
//If instance is MAP then call the recursive method
recusiveSerializer((Map) extension.getValue(), gen, serializers);
} else if (extension.getValue() instanceof String) {
//If instance is String directly add it to the JSON
gen.writeStringField(extension.getKey(), (String) extension.getValue());
} else if (extension.getValue() instanceof ArrayList) {
//If instance if ArrayList then loop over it and add it to the JSON after calling recursive method
gen.writeFieldName(extension.getKey());
gen.writeStartObject();
for (Object dupItems : (ArrayList<String>) extension.getValue()) {
if (dupItems instanceof Map) {
recusiveSerializer((Map) dupItems, gen, serializers);
} else {
gen.writeStringField(extension.getKey(), (String) extension.getValue());
}
}
gen.writeEndObject();
}
}
}
}
Try to set #JsonTypeInfo.include to JsonTypeInfo.As.EXISTING_PROPERTY
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, visible = true, property = "isA")

Deserializing json subtype based on parent property

I have a json coming with a dynamic attribute child, like below:
{
"label":"Some label",
"attribute": { <--- Dynamic attribute object
"type": "TEXT", <--- Field used to map `attribute` dynamic (inside child object)
"languages":[
{
"language":"en_EN",
"text":"do you approve?"
}
]
}
}
Or
{
"label":"Some label",
"attribute": {
"type": "NUMBER",
"value: "10.0"
}
}
I am able to deserialize above json properly using #JsonSubTypes with this code:
#Data
public class Field {
private String label;
private Attribute attribute;
}
#Data
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Attribute.NumberAttribute.class, name = "NUMBER"),
#JsonSubTypes.Type(value = Attribute.TextAttribute.class, name = "TEXT")
})
public class Attribute {
private AttributeType type;
#Data
public static class TextAttribute extends Attribute {
List<Language> languages;
}
#Data
public static class NumberAttribute extends Attribute {
String value;
}
#Data
public static class Language {
private String text;
private String language;
}
}
However, the problem I have is that I have to use type field coming inside the attribute object, and I would need to move the type to the parent object. The ending json should be like this:
{
"type": "TEXT", <--- Field used to map `attribute` dynamic (in parent object)
"label":"Some label",
"attribute": { <--- Dynamic attribute object
"languages":[
{
"language":"en_EN",
"text":"do you approve?"
}
]
}
}
Or
{
"type": "NUMBER",
"label":"Some label",
"attribute": {
"value: "10.0"
}
}
I couldn't find any way to use a parent field (or json path way) to use the type property being outside the dynamic subtype. Do you know how I can do this?
You can achieve that by adding include = As.EXTERNAL_PROPERTY to #JsonTypeInfo. You just have to move the annotation to the field.
See the JavaDoc for EXTERNAL_PROPERTY:
Inclusion mechanism similar to PROPERTY, except that property is included one-level higher in hierarchy [...]
Here's an example:
#Data
class Field {
private String label;
private AttributeType attributeType;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.EXTERNAL_PROPERTY, property = "attributeType")
private Attribute attribute;
}
#Data
#JsonSubTypes({
#JsonSubTypes.Type(value = Attribute.NumberAttribute.class, name = "NUMBER"),
#JsonSubTypes.Type(value = Attribute.TextAttribute.class, name = "TEXT")
})
abstract class Attribute {
#Data
public static class TextAttribute extends Attribute {
List<Language> languages;
}
#Data
public static class NumberAttribute extends Attribute {
String value;
}
#Data
public static class Language {
private String text;
private String language;
}
}
enum AttributeType {
NUMBER, TEXT;
}
I'm posting this as an alternative in case accepted answer doesn't work for others:
#Data
public class Field {
private String label;
private AttributeType type;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", include = As.EXTERNAL_PROPERTY)
#JsonSubTypes({
#JsonSubTypes.Type(value = Attribute.NumberAttribute.class, name = "NUMBER"),
#JsonSubTypes.Type(value = Attribute.TextAttribute.class, name = "TEXT")
})
private Attribute attribute;
}
#Data
public class Attribute {
#Data
public static class TextAttribute extends Attribute {
List<Language> languages;
}
#Data
public static class NumberAttribute extends Attribute {
String value;
}
#Data
public static class Language {
private String text;
private String language;
}
}

Jackson WRAP_ROOT_VALUE adds two root elements

I'm writing a REST API in Java and Play Framework, however I ran into a problem with Jackson serialization. I have the following model:
#Entity
#JsonRootName("country")
public class Country extends BaseModel<Country> {
private String name;
private Collection<City> cities;
...
}
The Jackson object mapper configuration:
ObjectMapper mapper = Json.newDefaultMapper()
.configure(SerializationFeature.WRAP_ROOT_VALUE, true)
.configure(SerializationFeature.INDENT_OUTPUT, true);
When I serialize a Country model however,
Country c = service.get(id);
return ok(toJson(c));
I get the following output:
{
"ObjectNode" : {
"country" : {
"id" : 5,
"name" : "Holandija",
"cities" : [ ]
}
}
}
The expected output would be:
{
"country" : {
"id" : 5,
"name" : "Holandija",
"cities" : [ ]
}
}
Why is Jackson adding the extra ObjectNode node? How to get rid of it?
It seems you have a problem in toJson method. The following code works perfect (the original class Country was modified for simplicity):
#Entity
#JsonRootName(value = "country")
public class Country {
public int id;
public String name;
public Collection<String> cities;
public Country() {
}
public Country(int id, String name) {
this.id = id;
this.name = name;
}
}
Test:
#Test
public void testRootJsonMapping() throws JsonProcessingException {
Country tested = new Country(55, "Neverland");
ObjectMapper mapper = new ObjectMapper()
.configure(SerializationFeature.WRAP_ROOT_VALUE, true)
.configure(SerializationFeature.INDENT_OUTPUT, true);
String json = mapper.writeValueAsString(tested);
System.out.println("json:" + json);
}
Test output:
json:{
"country" : {
"id" : 55,
"name" : "Neverland",
"cities" : null
}
}
If json conversion is done with Play API Json, it should be configured on startup with appropriate mapping options:
private void configureJson() {
ObjectMapper mapper = new ObjectMapper()
.configure(SerializationFeature.WRAP_ROOT_VALUE, true)
.configure(SerializationFeature.INDENT_OUTPUT, true);
Json.setObjectMapper(mapper);
}
Here you can read more details of how to customize Json conversion in Play.

Categories

Resources