Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert Play Framework Models into XML and JSON?

Does the Play Framework have a Native or Recommended way of converting Play models into XML/JSON? Something similar to JAXB or Jackson.

Some people recommend the template approach but this is very verbose and doesn't guarantee well-formed XML/JSON.

The Play Documentation on XML just shows an XML response being built using String concatenation like so:

return ok("<message \"status\"=\"OK\">Hello " + name + "</message>");

Similarly, the Play Documentation on JSON shows a JSON object being built up one line at a time.

ObjectNode result = Json.newObject();
result.put("status", "OK");
result.put("message", "Hello " + name);

Is there a Standard way of serializing Models into XML/JSON using Play?

Is there any Official Play Documentation on this subject?

like image 637
KevSheedy Avatar asked Feb 05 '13 18:02

KevSheedy


2 Answers

Short answer: Jackson for JSON and JAXB for XML

Play itself doesn't provide any documentation on marshalling models but it does ship with 3rd party libraries that can do the job.


JSON:

The model:

public class User extends Model {
    public String username;
    public Long   age; 

    @JsonIgnore
    public String password; // field won't be marshalled
}

Marshall it to JSON using jackson's ObjectMapper.writeValueAsString() method.

import org.codehaus.jackson.map.ObjectMapper;
//
ObjectMapper mapper     = new ObjectMapper();
String       jsonString = mapper.writeValueAsString(country);

JSON Output:

{
    "username" : "John Smith",
    "age"      : "25"
}

XML:

Care must be taken because of how Play generates getters and setters for it's models under the hood. You won't see the getter and setters in the code but they exist at runtime.

On the model, it's important to set the XmlAccessorType annotation to PROPERTY. This tells JAXB to serialize from the getter/setters and not from the underlying fields.

@XmlAccessorType(XmlAccessType.PROPERTY)

We also have to add an @XmlRootElement annotation which specifies the name of the root XML node:

@XmlRootElement(name = "UserRoot")

To omit a field, we must add the @XmlTransient annotation to the getter. Since there is no getter in the source code, we must add one for every field we want to omit.

@XmlAccessorType(XmlAccessType.PROPERTY)
public class User extends Model {
    public String username;
    public Long   age;

    @JsonIgnore
    public String password;


    @XmlTransient // This means ignore this property
    public String getPassword() {
        return this.password;
    }
}

The marshalling is performed by the JAXB classes Marshaller and JAXBContext

JAXBContext context    = JAXBContext.newInstance(User.class);
Marshaller  marshaller = context.createMarshaller();

// Use linefeeds and indentation in the outputted XML
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

marshaller.marshal(user, System.out);

Output:

<UserRoot>
    <name>John Smith</name>
    <age>25</age>
</UserRoot>

Summary:

The Play docs on XML and the Play docs on JSON do provide some information on working with json/xml but there doesn't seem to be any Play Docs describing how to do Marshalling. For that we have to look at 3rd Party libraries and documentation.

like image 140
KevSheedy Avatar answered Oct 11 '22 13:10

KevSheedy


For JSON I'd suggest using... org.codehaus.jackson, as it available as play.libs.Json in Play 2/x @see: Json Doc

For XML - template approach is fair enough, as you can render proper XML with the view.

Edit:

Json and Ebean

Sadly must to say that Ebean has a problems with serializing its objects to JSON, therefore I'm always using dedicated inner class (in the target model, which contains only fields, that should be send in Json) ie, for User model:

public static class ForJson {
    public Long id;
    public String name;
    public String email;

    public ForJson(User user) {
        this.id = user.id;
        this.name = user.name;
        this.email=user.email;
    }
}

routes:

GET     /users/all.json            controllers.Application.listUsersJson
GET     /users/all-details.json    controllers.Application.listUsersJsonWithDetails
GET     /users/:id.json            controllers.Application.singleUserJson(id: Long)

actions:

public static Result listUsersJson() {
    List<User.ForJson> usersToJson = new ArrayList<>();
    for (User user : User.find.all()) {
        usersToJson.add(new User.ForJson(user));
    }
    return ok(Json.toJson(usersToJson));
}

public static Result singleUserJson(Long id) {
    User.ForJson userForJson = new User.ForJson(User.find.byId(id));
    return ok(Json.toJson(userForJson));
}

public static Result listUsersJsonWithDetails() {
    Map<String, Object> details = new LinkedHashMap<>();

    List<User.ForJson> usersToJson = new ArrayList<>();
    for (User user : User.find.all()) {
        usersToJson.add(new User.ForJson(user));
    }

    details.put("date", new Date());
    details.put("count", usersToJson.size());
    details.put("users", usersToJson);

    return ok(Json.toJson(details));
}

Yes, I know maybe it's reduntand coding, but I have at least always proper JSON output, and I don't need to create JSON line by line in each action..

XML:

HTML chars won't break the rendering the proper XML as by default Play's templates escapes them, so instead of <, >, " it will use &lt;, &gt, &quot; inside the XML node:

<sample>Say &quot;ellou&quot;<sample>

Check Escaping paragraph in templates's doc (bottom of the page).

What's more you can use partial templates - tags to make sure, that single item will be formatted exactly the same in both: users/1.xml and users/all.xml

like image 35
biesior Avatar answered Oct 11 '22 12:10

biesior