I have JSON maps like this:
"things": {"foo": {"name": "foo", ...}, "bar": {"name": "bar", ...}}
I want to deserialize them as if they were arrays:
"things": [{"name": "foo", ...}, {"name": "bar", ...}]
(to match XML/JAXB deserialization behavior):
<things><thing name="foo">...</thing><thing name="bar">...</thing></things>
into a collection such as this:
#XmlElementWrapper
#XmlElement(name = "thing")
#JsonDeserialize(using = MapToCollectionDeserializer.class)
Collection<Thing> things;
Note that I have collections with various element types -- not just Thing -- so I need a generic mechanism.
However, when writing a custom deserializer, what's the right way to access the type information of the context?
public class MapToCollectionDeserializer extends StdDeserializer<Object>
{
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
Preconditions.checkState(jp.getCurrentToken() == JsonToken.START_OBJECT);
final LinkedList<Object> result = new LinkedList<>();
JsonToken tok;
while ((tok = jp.nextToken()) != JsonToken.END_OBJECT)
{
Preconditions.checkState(tok == JsonToken.FIELD_NAME);
// How to get the collection element type for deserialization?
result.add(...);
}
return result;
}
}
My approach so far is using ContextualDeserializer, which can provide a BeanProperty (which contains type information) to the deserializer. However, a JsonDeserializer must still have a no-arg constructor, so I end up constructing a broken object at first:
public class MapToCollectionDeserializer extends StdDeserializer<Object>
implements ContextualDeserializer<Object>
{
private final BeanProperty property;
public MapToCollectionDeserializer()
{
super(Collection.class);
property = null; // YUCK: BROKEN!!!
}
private MapToCollectionDeserializer(BeanProperty property)
{
super(property.getType());
this.property = property;
}
#Override
public JsonDeserializer<Object> createContextual(DeserializationConfig config,
BeanProperty property) throws JsonMappingException
{
return new MapToCollectionDeserializer(property);
}
#Override
public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException,
JsonProcessingException
{
Preconditions.checkState(jp.getCurrentToken() == JsonToken.START_OBJECT);
final JavaType elementType = property.getType().containedType(0);
final LinkedList<Object> result = new LinkedList<>();
JsonToken tok;
while ((tok = jp.nextToken()) != JsonToken.END_OBJECT)
{
Preconditions.checkState(tok == JsonToken.FIELD_NAME);
jp.nextToken();
final JsonDeserializer<Object> valueDeser = ctxt.getDeserializerProvider()
.findValueDeserializer(ctxt.getConfig(), elementType, property);
result.add(valueDeser.deserialize(jp, ctxt));
}
return result;
}
}
Is there a better/simpler way to do this?
It looks like you stopped using Jackson, but for anyone with a similar problem, you can turn on DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY. With that setting enabled, when Jackson finds an object in the JSON but is supposed to deserialize to a collection, it will create a collection and put the object into the collection which seems like what you want here.
Related
I'm using an api where a particular field (below) normally contains a string array. However, if the array is empty, the api returns an empty object in what is normally the array of string. Here is the field that's causing problems.
Normal.
"a": [
"str"
]
Empty.
"a": [
{}
]
The second case causes Gson to crash with a JsonSyntaxException. How do I handle this?
I don't know if this is the best way, but it works.
The faulty field can be annotated with #JsonAdapter(MyTypeAdapter.class). The TypeAdapter can then use its read method and check using peek() weather or not the next value is of the expected type.
Let us assume you have a class representing the API response, like:
public class Response {
private String[] a;
private String b;
private String c;
}
One way to get the Response object parsed whether JSON for a is valid or not is to create a JsonDeserializer that checks if a can parsed and excludes parsing of a if it fails, so leaves a to null.
public class SkipBadSyntaxDeserializer implements JsonDeserializer<Response> {
// This strategy is used if parse of field a fails
private final ExclusionStrategy excludeA = new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
return "a".equals(f.getName());
}
// no need to care of this used only here for the Response class
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
};
// one parser for good and another for bad format
private final Gson gson = new Gson(),
gsonBadFormat = new GsonBuilder()
.addDeserializationExclusionStrategy(excludeA).create();;
#Override
public Response deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException {
try {
return gson.fromJson(json, Response.class);
} catch (JsonSyntaxException e) {
// parse a failed try again without it
return gsonBadFormat.fromJson(json, Response.class);
}
}
}
Try it with:
new GsonBuilder().registerTypeAdapter(Response.class,
new SkipBadSyntaxDeserializer())
.create()
.fromJson(JSON, Response.class);
If JSON would be like:
{
"a": [{}],
"b": "bval",
"c": "cval"
}
then properties for Response would be:
a=null
b="bval"
c="cval"
Update
Based on your own answer: if it is possible to alter DTO for response then using annotation #JsonAdapter will let you to handle this per field. Deserializer will then be simply:
public class SkipExceptionAdapter implements JsonDeserializer<String[]> {
#Override
public String[] deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException {
try {
return context.deserialize(json, String[].class);
} catch (JsonSyntaxException e) {
return new String[] {}; // or null how you wish
}
}
}
and annotation in Response.a
#JsonAdapter(SkipExceptionAdapter.class)
private String[] a;
will handle it for that field only.
Consider a json of type "Clothing":
{
"id":"123",
"version":2,
"apparel":{
"category":[
{
"id":"a1",
"style":"top",
"comments":[
{
"header":{
"type":"apparel.detail.Summary",
"major_version":1,
"minor_version":0
},
"summary": "notes"
}]
}
]
},
"accessories":[
{
"header":{
"type":"accessories.detail.Handbag",
"major_version":1,
"minor_version":0
},
"details":{
"brand":"Gucci",
"sno.":"G12"
},
"color":"Red",
},
{
"header":{
"type":"accessories.detail.Hat",
"major_version":1,
"minor_version":0
},
"details":{
"brand":"Adidas",
"sno.":"A12"
}
}
]
}
"Clothing" is not accessible to me and I cannot add any field level or class level json annotations.
There is a property "header" in json that helps me to determine the type of class I want to convert that entity into. I will remove the header from my json once the class type is determined (since header is not defined in my target class type because of which deserialization will fail)
I need to write a custom deserializer that returns a generic class type object. It will check if there is header, fetch target class name, remove header and deserialize it to the fetched target class and return.
This is the code that I have written, but it does not work and I am not even sure if it is possible to have a custom deserializer injected in SimpleModule with a generic return type.
#Singleton
#Provides
private Transformer provideTransformer(final HeaderDeserializer headerDeserializer) {
final SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(Object.class, headerDeserializer);
mapper.registerModule(simpleModule);
}
#Singleton
#Provides
private HeaderDeserializer provideHeaderDeserializer(final ObjectMapper objectMapper) {
return new HeaderDeserializer(objectMapper);
}
#Singleton
#Provides
private ObjectMapper provideObjectMapper() {
final ObjectMapper mapper = new ObjectMapper()
// Tell object mapper how to handle joda-time.
.registerModule(new JodaModule())
// include non-null values only
.setSerializationInclusion(Include.NON_NULL)
// ensures that timezone is preserved
.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
return mapper;
}
My HeaderDeserializer looks something like this:
public class HeaderDeserializer<T> extends StdDeserializer<T> {
private static final long serialVersionUID = 1L;
private final ObjectMapper mapper;
public HeaderDeserializer(final ObjectMapper mapper) {
this(null, mapper);
}
public HeaderDeserializer(final Class<?> vc, final ObjectMapper mapper) {
super(vc);
this.mapper = mapper;
}
#Override
public T deserialize(final JsonParser jp, final DeserializationContext ctx) {
Object value = null;
try {
JsonNode node = this.mapper.readTree(jp);
JsonNode header = node.get("header");
if (node.has("header")) {
String targetClass = header.get("type").textValue();
removeHeaderFromJsonDoc(node);
value = this.mapper.readValue(jp, Class.forName(targetClass));
}
} catch (final IOException e) {
throw new UncheckedIOException(e);
} catch (final ClassNotFoundException e) {
// do somehting
}
return (T) value;
}
private void removeHeaderFromJsonDoc(final JsonNode document) {
final Iterator<Entry<String, JsonNode>> itr = document.fields();
while (itr.hasNext()) {
final Entry<String, JsonNode> childNodeEntry = itr.next();
if (childNodeEntry.getKey().equals("header")) {
itr.remove();
}
}
}
}
And my main deserializer which will use the custom deserializer defined above looks like:
public final Clothing deserialize(
final String stringValue,
final Class<? extends Clothing> clazz) {
try {
return this.objectMapper.readValue(stringValue, clazz);
} catch (final IOException e) {
throw new IllegalArgumentException();
}
}
this.objectMapper.readValue(stringValue, clazz);
Class type of 'clazz' in this readValue method should match class type passed in simpleModule.addDeserializer.
It is not going inside your deserializer because you are adding deserializer to SimpleModule for 'Object' class and reading value for different class passed to 'Clothing deserialize',
Please note: There are many questions on this site about how to use custom Jackson deserializers...this question is not one more of those! This questions has to do with using a deserializer under very unique circumstances (none of which have previous questions/answers on this site!).
Spring Boot using Jackson for JSON serialization here. I have two POJOs that are used in the #RequestBody (HTTP request entity) for a POST endpoint:
#JsonDeserialize(using = FizzDeserializer.class)
public class Fizz {
private String name;
private String label;
private Integer code;
// Getters, setters & ctors
}
#JsonDeserialize(using = BuzzDeserializer.class)
public class Buzz {
private String id;
private String locale;
private Set<Fizz> fizzes;
// Getters, setters & ctors
}
#RestController
#RequestMapping("v1/data/buzzes")
public class BuzzController {
#PostMapping
public void updateBuzz(#RequestBody Buzz buzz) {
// do whatever
}
}
I want HTTP clients to be able to POST the following JSON to this endpoint:
{
"id" : "12345-67890",
"locale" : "en_US",
"fizzes" : [
"foo",
"bar"
]
}
...where "foo" and "bar" are the Fizz#names of two different Fizz instances. In other words, I don't want the client to have to specify the entire Fizz object, just specify its name as a JSON string (my app + DB guarantee Fizzes have unique names).
So I'm using a custom JsonDeserializer to accomplish all this mapping:
public BuzzDeserializer extends JsonDeserializer<Buzz> {
Buzz deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode buzzNode = jsonParser.readValueAsTree();
String id = buzzNode.get("id");
String locale = buzzNode.get("locale");
// TODO: How to read "foo" and "bar" (etc.) into a Set<Fizz> instances?
Set<Fizz> fizzes = ???
new Buzz(id, locale, fizzes);
}
}
public FizzDeserializer extends JsonDeserializer<Fizz> {
private FizzDAO fizzDAO;
// Getters, setters & ctors...
Fizz deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode fizzNode = jsonParser.readValueAsTree();
// If I can get access to "foo"/"bar"/etc. string somehow, I can look up the Fizz using the DAO:
String fooBarEtcStr = ???
return fizzDAO.findFizzByName(fooBarEtcStr);
}
However I'm not sure how I can read the JSON fizzes array into a Set<Fizz> inside this deserializer. Any ideas?
Taken from the comments to the question, the only problem seems to be to get the JSON array. Provided that the service or DAO to lookup is already injected or provided in the serializer, try something along the lines:
final JsonNode arr = buzzNode.get("fizzes");
if (arr.isArray()) {
final Set<Fizz> fizzes = Sets.newHashSetWithExpectedSize(arr.size());
for (JsonNode obj : arr) {
final String name = obj.asText();
Fizz fizz = // load from DAO
fizzes.add(fizz);
}
}
This can of course be optimised by collecting the String values and use only one DAO call. Also some java8 streaming could make the code less verbose.
I have the following JSON:
{
"animals": {
"113110": {
"id": 113110,
"name": "Dog",
.....
},
"121853": {
"id": 121853,
"name": "Cat",
.....
}
}
}
Ideally, the JSON should be as follows and implementing Jackson annotations will be trivial:
{
"animals": [
{
"id": 113110,
"name": "Dog",
.....
},
{
"id": 121853,
"name": "Cat",
.....
}
]
}
However, is there a way to use Jackson to abstract the object names so I can work with the original JSON, if anybody gets my meaning?
EDIT:
I do not know how to create my POJO. I could create an Animal class, with objects 113110 and 121853, but as these objects will always vary, how do I use Jackson annotations in my Animal class so that I can deserialize the JSON?
Thanks all, but I couldn't really understand the rest of the answers ( I don't really want to delve into Jackson, I just want to convert it to a POJO), so I found an alternative solution.
I left out a key bit of information: The JSON I posted is part of a much larger JSON object.
I ended up using Jackson's #AnySetter as I noticed that any "un-parsable" JSON data related to "animals" could be retrieved in additionalProperties defined as follows in its parent class:
public class AnimalParent {
#JsonIgnore
private Animal animal;
#JsonIgnore
private Map<String, Object> additionalProperties =
new HashMap<String, Object>();
public Animal getAnimal() {
return this.animal;
}
public void setAnimal(Animal animal) {
this.animal = animal;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
Then in my main method where I parse the parent JSON, I have the following after the parsing is completed to parse the animals.
// start parsing parent JSON
...
// end parsing parent JSON
// parse animal
ObjectMapper mapper = new ObjectMapper();
if (animalParent.getAdditionalProperties() != null) {
for (Map.Entry<String, Object> item : animalParent
.getAdditionalProperties().entrySet()) {
Animal animal = mapper.convertValue(item.getValue(), Animal.class);
animalParent.setAnimal(animal);
}
It is always a bit messy when your JSON is "dynamic" as per the OPs example. The main approaches are
parse the JSON to some kind of dynamic Map-structure
parse the JSON to a tree-structure (i.e. JsonNode)
use a custom deserializer to parse the JSON and map it to a POJO
There are downsides to all of these approaches. The Map-approach offers no type safety and does not offer much functionality when it comes to traversing the object structure.
The JsonNode approach offers some nice type-methods and also some traversal methods. IMO this is a cleaner approach than the Map-approach.
The POJO-approach is type safe but a custom deserializer is required which is generally not pretty...
So, maybe the following "hybrid" approach can be of use.
// Setup the mapper
final ObjectMapper mapper = new ObjectMapper();
// Parse the json to a tree (JsonNode). This is IMO nicer than the
// Map since it exposes some nice methods for managing the
// underlying data
final JsonNode json = mapper.readTree(jsonString);
// Alt 1, use JsonNode directly
for (final JsonNode animal : json.path("animals")) {
final int id = animal.get("id").asInt();
final String name = animal.get("name").asText();
// Do stuff with name and id...
}
If the JsonNode approach feels a bit too raw then it is possible to convert the JsonNode object to a POJO without the use of a deserializer. If you assume the following POJO:
public class Animal {
private final int id;
private final String name;
#JsonCreator
public Animal(#JsonProperty("id") final int id, #JsonProperty("name") final String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
}
Then, this code can be used for converting to POJOs:
final ObjectMapper mapper = new ObjectMapper();
final JsonNode json = mapper.readTree(jsonString);
// Alt 2, convert to a Pojo
for (final JsonNode animal : json.path("animals")) {
final Animal a = mapper.treeToValue(animal, Animal.class);
// Handle the animal instance...
}
Finally, if the POJO still contains dynamic data you can use the following approach to handle that. In your POJO, declare the following:
private final Map<String, Object> dynamic = new HashMap<>();
#JsonAnySetter
private void set(String name, Object value) {
dynamic.put(name, value);
}
Note that it is not a must for the method to be public (i.e. it can be hidden from the outside world). This way you'll get hold of all the unknown/dynamic JSON elements.
Personally, any time I'm dealing with weird JSON that doesn't map easily to POJOs, I just do custom serialization.
I would probably make the POJOs look something like this:
public class Animal
{
String id;
String name;
}
public class JsonThing
{
List<Animal> animals;
}
Then I would implement a custom parser using the Jackson stream API. Here's a quick stub of a JsonDeserializer<JsonThing>:
public Stuff deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
.... // Start by creating a JsonThing instance and init the list.
while (jp.nextToken() != JsonToken.END_OBJECT)
{
jp.nextToken();
switch (jp.getCurrentName())
{
case "animals":
jp.nextToken(); // Skip to {
jp.nextToken(); // Skip id field
Animal a = jp.readValuesAs(Animal.class);
// Add to list
}
}
..... // Return JsonThing
}
If the keys are not known in advance then use Map instead of POJO.
Have a look at Example 1 and Example 2
You can try any one.
sample code: (using Jackson Library)
TypeReference<Map<String, Object>> typeRef = new TypeReference<Map<String, Object>>() {};
ObjectMapper mapper = new ObjectMapper();
try {
Map<String, Object> data = mapper.readValue(jsonString, typeRef);
} catch (Exception e) {
System.out.println("There might be some issue with the JSON string");
}
sample code: using GSON Library
Type type = new TypeToken<Map<String, Object>>() {}.getType();
Map<String, Object> data = new Gson().fromJson(jsonString, type);
Perhaps it's just a question of combining simple Maps with pojos? Like:
public class Wrapper {
public Map<Long, Animal> animals;
}
public class Animal {
public long id;
public String name;
}
and that's it; although ids there match, maybe there is no need to try to model that dependency.
I need to create custom jackson generic typed bean deserializer.
T is bean implementing IEntity.
public interface IEntity {
public int getId();
}
#JsonDeserialize(using = RestLoaderRequestDeserializer.class)
#Produces(MediaType.APPLICATION_JSON)
public class RestLoaderRequest<T extends IEntity> Serializable {
private T entity; // entity to load field to
private String className; // actual class of entity
private String fieldName; // fieldName to lazy REST load
// constructors(non parameter & all parameter), getters, setters, hashCode, equals, toString
}
Now what I have is this:
public class RestLoaderRequestDeserializer extends JsonDeserializer<RestLoaderRequest<IEntity>> {
#Override
public RestLoaderRequest<IEntity> deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
try {
ObjectCodec objectCodec = jp.getCodec();
JsonNode node = objectCodec.readTree(jp);
String className = node.get("className").textValue();
String fieldName = node.get("fieldName").textValue();
Class<?> clazz = Class.forName(className);
JsonNode rawEntityNode = node.get("entity");
// How to deserialize rawEntityNode to T based on className ?
RestLoaderRequest<IEntity> request = new RestLoaderRequest<IEntity>();
request.setClassName(className);
request.setFieldName(fieldName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
1) How to do auto-deserialization of entity based on className?
Keep in mind that handling of type information and handling of payload data are separate.
So regular JSON (de)serializers are not supposed to handle types at all: rather, separate TypeSerializer and TypeDeserializer are used. This is done to prevent explosion in number of combinations; there are couple of TypeSerializer/-Deserializer implementations (for different inclusion mechanism), but hundreds of value (de)serializers.
Custom (de)serializers can obviously break this separation, when you have full control. But core Jackson has this separation, and you can see examples in standard handlers.