IT박스

JAXB 객체 목록의 직렬화를 JSON으로 사용자 정의하려면 어떻게해야합니까?

itboxs 2021. 1. 11. 07:53
반응형

JAXB 객체 목록의 직렬화를 JSON으로 사용자 정의하려면 어떻게해야합니까?


Jersey를 사용하여 서버 구성 요소에 대한 REST 웹 서비스를 만들고 있습니다.

목록에서 직렬화하려는 JAXB 주석이 달린 객체는 다음과 같습니다.

@XmlRootElement(name = "distribution")
@XmlType(name = "tDistribution", propOrder = {
    "id", "name"
})
public class XMLDistribution {
    private String id;
    private String name;
    // no-args constructor, getters, setters, etc
}

다음과 같은 배포판 하나를 검색하는 REST 리소스가 있습니다.

@Path("/distribution/{id: [1-9][0-9]*}")
public class RESTDistribution {
    @GET
    @Produces("application/json")
    public XMLDistribution retrieve(@PathParam("id") String id) {
        return retrieveDistribution(Long.parseLong(id));
    }
    // business logic (retrieveDistribution(long))
}

또한 다음과 같은 모든 배포 목록을 검색하는 REST 리소스가 있습니다.

@Path("/distributions")
public class RESTDistributions {
    @GET
    @Produces("application/json")
    public List<XMLDistribution> retrieveAll() {
        return retrieveDistributions();
    }
    // business logic (retrieveDistributions())
}

ContextResolver를 사용하여 현재 다음과 같이 구성된 JAXB 직렬화를 사용자 정의합니다.

@Provider
@Produces("application/json")
public class JAXBJSONContextResolver implements ContextResolver<JAXBContext> {
    private JAXBContext context;
    public JAXBJSONContextResolver() throws Exception {
        JSONConfiguration.MappedBuilder b = JSONConfiguration.mapped();
        b.nonStrings("id");
        b.rootUnwrapping(true);
        b.arrays("distribution");
        context = new JSONJAXBContext(b.build(), XMLDistribution.class);
    }
    @Override
    public JAXBContext getContext(Class<?> objectType) {
        return context;
    }
}

두 REST 리소스와 컨텍스트 해석기가 작동합니다. 다음은 첫 번째 출력의 예입니다.

// path: /distribution/1
{"id":1,"name":"Example Distribution"}

정확히 내가 원하는 것입니다. 다음은 목록 출력의 예입니다.

// path: /distributions
{"distribution":[{"id":1,"name":"Sample Distribution 1"},{"id":2,"name":"Sample Distribution 2"}]}

내가 원하는 것이 아닙니다.

I don't understand why there is an enclosing distribution tag there. I wanted to remove it with .rootUnwrapping(true) in the context resolver, but apparently that only removes another enclosing tag. This is the output with .rootUnwrapping(false):

// path: /distribution/1
{"distribution":{"id":1,"name":"Example Distribution"}} // not ok
// path: /distributions
{"xMLDistributions":{"distribution":[{"id":1,"name":"Sample Distribution 1"},{"id":2,"name":"Sample Distribution 2"}]}}

I also had to configure .arrays("distribution") to always get a JSON array, even with only one element.

Ideally, I'd like to have this as an output:

// path: /distribution/1
{"id":1,"name":"Example Distribution"} // currently works
// path: /distributions
[{"id":1,"name":"Sample Distribution 1"},{"id":2,"name":"Sample Distribution 2"}]

I tried to return a List<XMLDistribution>, a XMLDistributionList (wrapper around a list), a XMLDistribution[], but I couldn't find a way to get a simple JSON array of distributions in my required format.

I also tried the other notations returned by JSONConfiguration.natural(), JSONConfiguration.mappedJettison(), etc, and couldn't get anything resembling what I need.

Does anyone know if it is possible to configure JAXB to do this?


I found a solution: replace the JAXB JSON serializer with a better behaved JSON serializer like Jackson. The easy way is to use jackson-jaxrs, which has already done it for you. The class is JacksonJsonProvider. All you have to do is edit your project's web.xml so that Jersey (or another JAX-RS implementation) scans for it. Here's what you need to add:

<init-param>
  <param-name>com.sun.jersey.config.property.packages</param-name>
  <param-value>your.project.packages;org.codehaus.jackson.jaxrs</param-value>
</init-param>

And that's all there is to it. Jackson will be used for JSON serialization, and it works the way you expect for lists and arrays.

The longer way is to write your own custom MessageBodyWriter registered to produce "application/json". Here's an example:

@Provider
@Produces("application/json")
public class JsonMessageBodyWriter implements MessageBodyWriter {
    @Override
    public long getSize(Object obj, Class type, Type genericType,
            Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    @Override
    public boolean isWriteable(Class type, Type genericType,
            Annotation annotations[], MediaType mediaType) {
        return true;
    }

    @Override
    public void writeTo(Object target, Class type, Type genericType,
            Annotation[] annotations, MediaType mediaType,
            MultivaluedMap httpHeaders, OutputStream outputStream)
            throws IOException {        
        new ObjectMapper().writeValue(outputStream, target);
    }
}

You'll need to make sure your web.xml includes the package, as for the ready-made solution above.

Either way: voila! You'll see properly formed JSON.

You can download Jackson from here: http://jackson.codehaus.org/


The answer of Jonhatan is great and it has been very useful for me.

Just an upgrade:

if you use the version 2.x of Jackson (e.g. version 2.1) the class is com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider, therefore the web.xml is:

<init-param>
  <param-name>com.sun.jersey.config.property.packages</param-name>
  <param-value>your.project.packages;com.fasterxml.jackson.jaxrs.json</param-value>
</init-param>

ReferenceURL : https://stackoverflow.com/questions/2199453/how-can-i-customize-serialization-of-a-list-of-jaxb-objects-to-json

반응형