24

I'm trying to read a legacy JSON code using Jackson 2.0-RC3, however I'm stuck with an "embedded" object.

Given a following JSON:

{
    "title": "Hello world!",
    "date": "2012-02-02 12:23:34".
    "author": "username",
    "author_avatar": "http://.../",
    "author_group": 123,
    "author_prop": "value"
}

How can I map it into the following structure:

class Author {
    @JsonPropery("author")
    private String name;

    @JsonPropery("author_avatar")
    private URL avatar;

    @JsonProperty("author_group")
    private Integer group;

    ...
}

class Item {
    private String title;

    @JsonProperty("date")
    private Date createdAt;

    // How to map this?
    private Author author;
}

I was trying to do that with @JsonDeserialize but it seems that I'd have to map the entire Item object that way.

Crozin
  • 43,890
  • 13
  • 88
  • 135

3 Answers3

51

To deal with an "embedded" object you should use @JsonUnwrapped — it's an equivalent of the Hibernate's @Embeddable/@Embedded.

class Item {
    private String title;

    @JsonProperty("date")
    private Date createdAt;

    // How to map this?
    @JsonUnwrapped
    private Author author;
}
Elnur Abdurrakhimov
  • 44,533
  • 10
  • 148
  • 133
Bruno Hansen
  • 534
  • 4
  • 2
1

I would deserialize the original JSON to a single, flat object first (kind of like an adapter), then create your own domain objects.

class ItemLegacy {
    private String title;

    @JsonProperty("date")
    private Date createdAt;

    @JsonPropery("author")
    private String name;

    @JsonPropery("author_avatar")
    private URL avatar;

    @JsonProperty("author_group")
    private Integer group;

    @JsonProperty("author_prop")
    private Integer group;
}

Then use this object to fill out your Item and Author objects and create the correct relationships.

 //... the deserialized original JSON
 ItemLegacy legacy ...

 // create an author
 Author author = new Author();
 author.setName(legacy.getName());
 author.setGroup(legacy.getGroup());
 ...

 // create an item
 Item item = new Item();
 item.setTitle(legacy.getTitle());
 ...

 // finally set the author... and you should have the desired structure
 item.setAuthor(author);

Your Item class could only be automatically deserialized from the following form:

{
    "title": "Hello world!",
    "date": "2012-02-02 12:23:34".
    "author": { 
                "name": "username", 
                "author_avatar": "http://...", 
                "author_group": "123", 
                "author_prop": "value" 
              }
}

You might be able to do something with custom deserialization, but it would not be the simpler solution for sure.

c_maker
  • 19,076
  • 1
  • 24
  • 29
  • I don't need to serialize to the proper JSON. All I need is to **deserialize** from legacy JSON into well-formed objects. – Crozin Apr 05 '12 at 23:53
  • Oh, now I get it. I was hoping that Jackson provides an equivalent of the Hibernate's `@Embeddable`/`@Embedded`. – Crozin Apr 06 '12 at 00:51
  • @Crozin: I see what you mean. If there is such a feature, I am unaware of it. – c_maker Apr 06 '12 at 00:56
0

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB 2 (JSR-222) expert group.

I'm not sure if Jackson supports this use case, but below is an example of how you can leverage MOXy's @XmlPath extension to meet your requirements. Note you will need to use an EclipseLink 2.4.0 nightly label from April 7, 2012 or newer.

Item

The author property on Item is mapped with @XmlPath('.'). This means that the content of Author is pulled up to the same level as the content for Item. I also needed to use an XmlAdapter for the Date property as the format you are using doesn't match MOXy's default representation.

package forum10036530;

import java.util.Date;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.eclipse.persistence.oxm.annotations.XmlPath;

@XmlAccessorType(XmlAccessType.FIELD)
class Item {
    private String title;

    @XmlElement(name="date")
    @XmlJavaTypeAdapter(DateAdapter.class)
    private Date createdAt;

    @XmlPath(".")
    private Author author;
}

Author

package forum10036530;

import java.net.URL;
import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
class Author {
    @XmlElement(name="author")
    private String name;

    @XmlElement(name="author_avatar")
    private URL avatar;

    @XmlElement(name="author_group")
    private Integer group;

    @XmlElement(name="author_prop")
    private String prop;
}

DateAdapter

package forum10036530;

import java.text.SimpleDateFormat;
import java.util.Date;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class DateAdapter extends XmlAdapter<String, Date> {

    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    @Override
    public Date unmarshal(String string) throws Exception {
        return dateFormat.parse(string);
    }

    @Override
    public String marshal(Date date) throws Exception {
        return dateFormat.format(date);
    }

}

jaxb.properties

A file called jaxb.properties with the following entry must be placed in the same package as the domain classes to specify MOXy as the JAXB (JSR-222) provider.

javax.xml.bind.context.factory = org.eclipse.persistence.jaxb.JAXBContextFactory

input.json/Output

{
   "title" : "Hello world!",
   "date" : "2012-02-02 12:23:34",
   "author" : "username",
   "author_avatar" : "http://www.example.com/foo.png",
   "author_group" : 123,
   "author_prop" : "value"
}

For More Information

bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • 1
    I haven't tested that yet, but this is exactly what I was looking for. – Crozin Apr 06 '12 at 20:40
  • @Crozin - I had to put in a fix to handle URL property so the complete example will require tonights build, but everything else works with the current build. – bdoughan Apr 06 '12 at 20:42