How to unwrap single item array and extract value field into one simple field? - jackson

I have a JSON document similar to the following:
{
"aaa": [
{
"value": "wewfewfew"
}
],
"bbb": [
{
"value": "wefwefw"
}
]
}
I need to deserialize this into something more clean such as:
public class MyEntity{
private String aaa;
private String bbb;
}
What's the best way to unwrap each array and extract the "value" field on deserialization? Just custom setters? Or is there a nicer way?

For completeness, if you use jackson, you can enable the deserialization feature UNWRAP_SINGLE_VALUE_ARRAYS.
To do that, you have to enable it for the ObjectMapper like so:
ObjectMapper objMapper = new ObjectMapper()
.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
With that, you can just read the class as you are being used to in Jackson.
For example, assuming the class Person:
public class Person {
private String name;
// assume getter, setter et al.
}
and a json personJson:
{
"name" : [
"John Doe"
]
}
We can deserialize it via:
ObjectMapper objMapper = new ObjectMapper()
.enable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);
Person p = objMapper.readValue(personJson, Person.class);

Quick solution with Gson is to use a JsonDeserializer like this:
package stackoverflow.questions.q17853533;
import java.lang.reflect.Type;
import com.google.gson.*;
public class MyEntityDeserializer implements JsonDeserializer<MyEntity> {
public MyEntity deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
String aaa = json.getAsJsonObject().getAsJsonArray("aaa").get(0)
.getAsJsonObject().get("value").getAsString();
String bbb = json.getAsJsonObject().getAsJsonArray("bbb").get(0)
.getAsJsonObject().get("value").getAsString();
return new MyEntity(aaa, bbb);
}
}
and then use it when parsing:
package stackoverflow.questions.q17853533;
import com.google.gson.*;
public class Q17853533 {
public static void main(String[] arg) {
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(MyEntity.class, new MyEntityDeserializer());
String testString = "{ \"aaa\": [{\"value\": \"wewfewfew\" } ], \"bbb\": [ {\"value\": \"wefwefw\" } ] }";
Gson gson = builder.create();
MyEntity entity= gson.fromJson(testString, MyEntity.class);
System.out.println(entity);
}
}

Related

Webclient serializing booleans with initial underscore

public MyClass {
private String field1;
private String field2;
private boolean field3;
public MyClass() {
this.field1 = "value1";
this.field2 = "value2";
this.field3 = false;
}
getters/setters...
}
When passing it to Webclient as the body of a post, the JSON that gets generated is:
{
"field1": "value1",
"field2": "value2",
"_field3": on
}
with field3's name changed and having a value that is always 'on'.
I haven't found a way to disable this: is it possible? Am I doing something wrong?
I tried adding #JsonProperty to the field and #JsonObject to the class but nothing changed. I'd expect the Json to be:
{
"field1": "value1",
"field2": "value2",
"field3": false
}
which is what the webservice is expecting.

Jackson parse JSON into Java Map

I have such JSON:
{
"list": [
{
"product": {
"id": 1123456,
"context": {
}
},
"items": [
]
},
and a code that reads it:
TypeReference<HashMap<String, Object>> typeRef
= new TypeReference<HashMap<String, Object>>() {};
InputStream inputStream = TypeReference.class.getResourceAsStream("/mocks/Docs.json");
Map<String, Object> map = mapper.readValue(inputStream, typeRef);
But I don't want the simple Map<String, Object>, I want to map into a map that looks like Map<String, MyRepresentation> map:
Is there a direct way to do it, or I need first to read it into Map<String, Object> and then manipulate it manually and fill the MyRepresentation object?
the JSON file structure doesn't correspond to the classes at all.
ConditionSummary looks like a type of contextData element accessible as docList[i].product.contextData if it had id, which is defined in the product element.
also, AccountManagerStatistics#map is not public and doesn't have #JsonProperty annotation, so it is out of json for now.
try creating the sample file first if you sure the classes represent the truth:
Map<String, AccountManagerStatistics> map = createStubData();
new ObjectMapper().writerFor(new TypeReference<Map<String, AccountManagerStatistics>>() {}).writeValueAsString(map)
or try to modify your classes to match the data,
which is probably what should be done here.
then you could start from the top and introduce a proper class instead of using Map<String, X>,
BTW no need for HashMap in TypeReference:
//#XmlRootElement
public class Root {
#JsonProperty("docList") //or #XmlElement("docList")
public final List<Doc> docs;
...
#ConstructorProperties({ "docs", ... })
public Root(List<Doc> docs, ...) {
this.docs = List.copyOf(docs);
...
}
}

JAX-RS ExceptionMapper throws MessageBodyProviderNotFoundException

Using JAX-RS, I have successfully implemented an ExceptionMapper for Exceptions that do not require a more sophisticated response than an HTTP status code, as follows.
#Provider
public class ISBNNotFoundManager implements ExceptionMapper<ISBNNotFoundException>{
#Override
public Response toResponse(ISBNNotFoundException exception) {
return Response.status(NOT_FOUND).build();
}
}
This works as expected.
However, I want to respond with something more useful when bean validation fails. The follow code snippet results in a MessageBodyProviderNotFoundException.
#Provider
public class ConstraintViolationExceptionMapper implements
ExceptionMapper<ConstraintViolationException> {
#Override
#Produces(MediaType.APPLICATION_JSON)
public Response toResponse(ConstraintViolationException exception) {
final Map<String, String> errorResponse =
exception.getConstraintViolations()
.stream()
.collect(
Collectors.toMap(o -> o.getPropertyPath().toString(), o -> o.getMessage()));
return Response.status(Response.Status.BAD_REQUEST).entity(errorResponse).build();
}
}
When a bean validation occurs the response includes the HTTP response code 500 and the root cause is given as follow:
org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException:
MessageBodyWriter not found for media type=application/json,
type=class java.util.HashMap, genericType=class java.util.HashMap.
What I have tried that didn't work:
Wrapping the Map in a GenericEntity like so. The same result as above:
new GenericEntity>(errorResponse) {}
What I tried the DID work:
Wrapping the map in a custom POJO, DataIntegrityValidation, as follows:
#XmlRootElement
public class DataIntegrityValidation {
private Map<String, String> errorResponse = new HashMap<>();
public Map<String, String> getErrorResponse() {
return errorResponse;
}
public void setErrorResponse(Map<String, String> errorResponse) {
this.errorResponse = errorResponse;
}
}
Then in the toResponse method I wrap the map in the DataIntegrityValidation POJO like so and add it to the response object.
DataIntegrityValidation dataIntegrityValidation =
new DataIntegrityValidation();
dataIntegrityValidation.setErrorResponse(errorResponse);
return
Response.status(Response.Status.BAD_REQUEST)
.entity(dataIntegrityValidation).build();
This gives the following JSON:
{
"errorResponse": {
"entry": [
{
"key": "saveBook.arg0.description",
"value": "size must be between 100 and 2147483647"
},
{
"key": "saveBook.arg0.published",
"value": "must be in the past"
},
{
"key": "saveBook.arg0.link",
"value": "must match \"^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w \\.-]*)*\\/?$\""
}
]
}
}
I can live with this but would really like to know why it cannot handle the Map even though it is wrapped in the Generic Entity.
All responses welcome.
The reason the marshalling failed for both Map and GenericEntity is because there is no JAXB definition associated with them. And when you wrapped the map in a POJO annotated with #XmlRootElement; it was able to marshal it correctly.

ignoring unknown fields on JSON objects using jackson

I am using jackson 2.x for serialization and deserialization. I am registered the objectMapper to the afterBurner module and configured the object mapper to ignore unknown properties
objectMapper.registerModule(new AfterBurnerModule());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
but when it is trying to serialize an object it is failing with unknown field found for attribute error
The java object is also annotated with #JsonIgnoreProperties(ignoreUnknown = true)
Can some one help me understand what might be going wrong
Below is the Util class
package example;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
public final class Util {
private static ObjectMapper objectMapper;
static {
objectMapper = new ObjectMapper();
objectMapper.registerModule(new AfterburnerModule());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
objectMapper.setDateFormat(sdf);
objectMapper.setAnnotationIntrospector(AnnotationIntrospector.pair(new JaxbAnnotationIntrospector(objectMapper.getTypeFactory()), new JacksonAnnotationIntrospector()));
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(Include.NON_NULL);
}
private Util() {
}
public static <T> T convertToObject(String jsonString,Class<T> classType){
T obj = null;
try {
obj = objectMapper.readValue(jsonString, classType);
} catch (Exception e) {
}
return obj;
}
public static String convertToString(Object obj)
throws IOException {
return objectMapper.writeValueAsString(obj);
}
}
enum class NumberEnum
package sample;
public enum NumberEnum {
ONE, TWO
}
class A
package sample;
#JsonIgnoreProperties(ignoreUnknown = true)
public class A {
#JsonProperty
private NumberEnum number;
}
the code where i am deserializing is as below
A a = Util.convertToObject(str, A.class);
and the string i am trying to deserailize is as below:
{
"number": "Num"
}
Below is the error while deserializing:
com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not construct instance of sample.NumberEnum from String value 'Num': value not one of declared Enum instance names: [ONE, TWO]
at (through reference chain: sample.A["a"]->sample.NumberEnum["number"])
the class A is imported from a jar and it is using jackson library 1.9
ignoreUnknown only applies to property names that are unknown in the destination object. For example, if you had:
{
"number": "ONE",
"foo": "bar"
}
Jackson would normally fail if the object you're trying to deserialize had no setter/property named "foo".
What you're trying to accomplish is entirely different; the property is known, but you're trying to handle an invalid enum value. If you just want it to deserialize unknown values as null, use READ_UNKNOWN_ENUM_VALUES_AS_NULL:
Feature that allows unknown Enum values to be parsed as null values. If disabled, unknown Enum values will throw exceptions. (...) Feature is disabled by default.
This is done via mapper configuration:
objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
Note: I just saw that you're using Jackson 1.9, and this deserialization feature was released in 2.0. If upgrading is not an option, you might need to create a custom deserializer for this property which does the same thing.
I think what you need to deserialize is actually json that looks like this:
{
"number": "ONE"
}
- OR -
{
"number": "TWO"
}
since "Num" is not a the name() of either of your enums it will not deserialize

Data Weave Pojo to Pojo Mappings

I am trying to do a Pojo to Pojo transformation as below( each Pojo has a List of objects) - the output is transformed but is skipping an object - sample code below.
public class Pojo1 implements Serializable {
private List<Foo> fooList =new ArrayList<Foo>(1);
public Pojo1() {
}
public List<Foo> getFooList() {
return fooList ;
}
public void setFooList(List<Foo> fooList) {
this.fooList= fooList;
}
}
public class Pojo2 implements Serializable {
private List<Bar> barList =new ArrayList<Bar>(1);
public Pojo2() {
}
public List<Bar> getBarList() {
return barList ;
}
public void setBarList(List<Bar> barList) {
this.barList= barList;
}
}
DataWeave transformation as follows - this works fine but only one object in the List gets transformed and I'm missing the second one.
barList: [{
(
payload.fooList map {
item1:$.item1
}
)
} as :object {
class :"com.fooClass"
}]
} as :object {
class : "com.barClass"
}
Thanks in advance !
Try with
{
barList: in1.fooList map {
item1: $.item1
} as :object {class : "com.fooClass"}
} as :object {class : "com.barClass"}
Note: as :object {class : "com.fooClass"} should be optional, as it can be inferred
The problem seems to be that you defined an array with only one object and filled that object with the result of:
payload.fooList map {
item1:$.item1
}
Which is an array of objects:
[
{
item1: "value1"
},
{
item1: "value2"
}
]
After filling the object with that, it ends up like:
{
item1: "value1",
item1: "value2",
item1: ...
}
So, you are filling the array with an object with a lot of duplicated fields (with different values) and when that output is written (since it's java) each one of those fields only get one value.