Serialize the whole class as a String field instea

2019-08-11 11:54发布

问题:

I have an object that uses a class instance as a wrapper for the String type. How would I serialize my class into a JSON object using JAX-RS and Java EE 6 by using only the toString() method without creating a JSON object for the class and each fields?

Class to be serialized:

public class Text {
    private String content;
    protected Text( String content ) {
        this.content = content;
    }
    public static Text from( String content ) {
        return new Text( "The text content");
    }
    // This is a method that is used by the subclass and has no meaning for serialization
    protected String getContent() {
        return content;
    }
    // This should be used to serialize this class into a String, instead of an object
    @Override
    public String toString() {
        return content;
    }
}

Parent class that uses the class to be serialized:

public class EditorStoryChapterInput {
    private Text title;
    public Text getTitle() {
        return title;
    }
}

Result expected: I want to serialize as { "title": "The text content" } instead of { "title": { content: "the text content" } }.

EDIT: Looking in the Jackson FAQ I found something useful, and what I want is probably the equivalent of what I found, but inside a JAX-RS/Java EE 6 environment in JBoss EAP 6.1. There is nothing as @JsonValue in my classpath using JBoss EAP 6.1:

http://wiki.fasterxml.com/JacksonFAQ#Serializing_values_as_simple_types

回答1:

From Comments:

I don't know about spec compliant, but I just tested it, adding the Jackson dependency to my project, and just putting the @JsonValue over the toString() like mentioned in the link, and it works fine. Like I said, JBoss is using Jackson under the hood to do the serializing. So there's nothing wrong with using it in your project. You just need to add the dependency in order to compile the annotation. You can get it straight from the server modules if you need to do. But it'll be easier to just use Maven

Here are the classes I used for the example. I'll post the classes, then I point out how to get the dependencies.

Your Text class

/** Getter and Setters added for Jackson discovery **/
public class Text {

    private String content;

    public void setContent(String content) { this.content = content; }
    protected String getContent() { return content; }

    @Override @JsonValue
    public String toString() { return content; }
}

Your EditorStoryChapterInput class

public class EditorStoryChapterInput {

    private Text title;

    public void setTitle(Text title) { this.title = title; }
    public Text getTitle() { return title; }
}

The resource class

@Path("/json")
public class JsonResource {
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response getJson() {
        EditorStoryChapterInput input = new EditorStoryChapterInput();
        Text title = new Text();
        title.setContent("Hello World");
        input.setTitle(title);
        return Response.ok(input).build();
    }
}

The Application class to register out resource

@ApplicationPath("/rest")
public class JsonApplication extends Application  {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> classes = new HashSet<Class<?>>();
        classes.add(JsonResource.class);
        return classes;
    }
}

Result

Get the Dependencies

If you are using Maven, you just need the jackson-core-asl artifact. When I use JBoss, I prefer to use their boms, so they can manage the versions. So What your pom would look something like (Remember I said we can use provide scope, because JBoss already has these dependencies. We just need it to compile)

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.jboss.bom</groupId>
            <artifactId>jboss-javaee-6.0-with-resteasy</artifactId>
            <version>1.0.7.Final</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-core-asl</artifactId>
        <scope>provided</scope>
    </dependency>
    ... Other dependencies
</dependencies>

If you're not using Maven, you can simply look inside

${eap-home-dir}\modules\system\layers\base\org\codehaus\jackson\jackson-core-asl\main

There you will find the jackson-core-asl-1.9.9.redhat-3.jar. Just add that to your project however you normally add jars to your project. Tested this approach, with same result

Let me know if you have any problems adding the dependency.


UPDATE: Without using Jackson (no third party dependencies)

We can use the Java API for JSON Processing, and implement our own MessageBodyWriter, which will be used from writing the EditorStoryChapterInput object to the response OutputStream. It might look something like

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class TextMessageBodyWriter 
    implements MessageBodyWriter<EditorStoryChapterInput> {

    @Override
    public boolean isWriteable(Class type, Type type1, 
            Annotation[] antns, MediaType mt) {
        return type == EditorStoryChapterInput.class;
    }

    @Override
    public long getSize(EditorStoryChapterInput t, Class<?> type, 
            Type type1, Annotation[] antns, MediaType mt) {
        return -1;
    }

    @Override
    public void writeTo(EditorStoryChapterInput input, Class<?> type,
            Type type1, Annotation[] antns, MediaType mt, 
            MultivaluedMap<String, Object> mm, OutputStream out) 
            throws IOException, WebApplicationException {

        Text title = input.getTitle();
        JsonObject jsonObject = 
                Json.createObjectBuilder()
                .add("title", title.toString()).build();

        try (JsonWriter jsonWriter = Json.createWriter(out)) {
            jsonWriter.writeObject(jsonObject);
        }
    }
}

When searching for MessageBodyWriters, JAX-RS will look at the @Produces annotation to see what type it produces, and if it matches the @Produces method on our resource method, the writer will be put into a list for the framework to traverse. Next it will check the isWritable method on each of the writers. If it returns true, then that's the writer it will use. In our case, we return true if the return type of the response body is of EditorStoryChapterInput type.

In the writeTo method, we use the Java JSON Processing API to create a JsonObject and write it to the OutputStream provided to us by the framework.

Then we can register the writer with the application

@ApplicationPath("/rest")
public class TextApplication extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        final Set<Class<?>> classes = new HashSet<>();
        classes.add(JsonResource.class);
        classes.add(TextMessageBodyWriter.class);
        return classes;
    }
}

Testing it with the same JsonResource class from above, we get the output

{"title":"Hello World"}

  • Jersey has a good tutorial for MessageBodyWriters/MessageBodyReaders
  • Resteasy's tutorial is not as well explained as Jersey's
  • Also see Java EE's tutorial for JSON Processing