How to use Jackson Annotations to process JSON with random object names - json

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.

Related

Hnadle Any JSON definition in Spring Rest controller from a request

I'm handling A JSON sent in POST Request body using:
Controller
#PostMapping
public Library newLibrary(#RequestBody Library newLibrary) {
return libraryRepository.saveAndFlush(newLibrary);
}
Library model
#Column(name = "Name", nullable = false)
private String name;
JSON in Request Body
{
"name" : "testLibrary"
}
But my application won't receive definite JSON Structure in a call, for example, I receive something like this -
{
"names" : ["testLibrary","testLibrary2"],
"anyKey" : "val",
"anykey2" : {"keys":"vals"}
}
So I need to know how can I handle this situation in a single Post mapping function.
You can use #JsonAnySetter annotation from jackson library.
Inside your model:
private Map<String, Object> details;
#JsonAnySetter
public void setDetail(String key, Object value) {
if (null == details) {
this.details = new HashMap<>();
}
this.details.put(key, value);
}
And it will put extra(not defined into model) fields into the map.
Hope this helps!

Is there a way to detect unmapped json properties when #JsonIgnoreProperties(ignoreUnknown = true) is set?

I need to detect which json fields are not mapped to the data model after PUT or POST requests.
For example:
If I post this:
{
"firstName": "test",
"lastName": "test 2",
"age": 25
}
and my model only have firstName and lastName, I want to list all unmapped fields, which in this example is "age" field.
Yes, that is possible using Jackson's annotation #JsonAnySetter
#JsonIgnoreProperties(ignoreUnknown = true)
public class DTO {
private String first;
private String last;
private Map<String, Object> unknown = new HashMap<>();
// getters/setters omitted
#JsonAnySetter
public void set(String name, Object value) {
unknown.put(name, value);
}
public Map<String, Object> getUnknown() {
return unknown;
}
}
Simple test:
#Test
public void testUnknown() throws Exception {
String json = "{\"first\":\"John\", \"last\":\"Doe\", \"age\":\"29\"}";
DTO dto = new ObjectMapper().readValue(json, DTO.class);
assertEquals(1, dto.getUnknown().size());
assertEquals("29", dto.getUnknown().get("age"));
}
If it's just about learning which properties are unmapped you may want to consider using this library: https://github.com/whiskeysierra/jackson-module-unknown-property
It logs unmapped properties for all mapped classes without a need to modify class itself.

Gson: Is there more efficient ways to do custom (de)serialization of a verbose Json object

I am working with some json objects that I call verbose:
{
"user": {
"name": "username",
"email": "blah#blah.com",
"time_zone": "America/New_York"
}
}
But I'd prefer to just deal with them in terms of java POJOs like:
class UserDetails {
String name;
String email;
String timeZone;
...
}
Note that I have no control over the POJO as it is generated code.
My two requirements for (de)serialization is that
the timeZone field maps to time_zone in JSON
the outer user is ignored
So I have some customer (de)serializers:
class UserDeserializer implements JsonDeserializer<UserDetails> {
#Override
public UserDetails deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException {
JsonElement content = je.getAsJsonObject().get("user");
UserDetails userDetails = new GsonBuilder()
.setFieldNamingStrategy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create()
.fromJson(content, UserDetails.class);
return userDetails;
}
}
class UserSerializer implements JsonSerializer<UserDetails> {
#Override
public JsonElement serialize(UserDetails userDetails, Type typeOfSrc,
JsonSerializationContext context) {
JsonObject obj = new JsonObject();
JsonElement je = new GsonBuilder()
.setFieldNamingStrategy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create().toJsonTree(userDetails);
obj.add("user", je);
return obj;
}
}
I feel like creating new Gson objects in the (de)serializer logic is not ideal/efficient just to add and remove the outermost user key.
EDIT: Actually .setFieldNamingStrategy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) does work fine on deserialization.
I don't really think it's a good idea in general, and you should probably better have a single Wrapper<T> for all "top-most" purposes (if you don't want your inner objects to be considered "verbose").
But you're right when you say
I feel like creating new Gson objects in the (de)serializer logic is not ideal/efficient just to add and remove the outermost user key.
So:
Creating a Gson is a relatively expensive operation.
This just creates unnecessary objects and hits the heap.
Gson may be configured in a special way and you might want to share the same Gson configuration everywhere.
JsonSerializer and JsonDeserializer operate on JSON trees (JsonElement and its subclasses), therefore it creates an intermediate in-memory tree representations before/after serialization/deserialization.
You might consider a faster solution, that's free of those items.
final class VerboseTypeAdapterFactory
implements TypeAdapterFactory {
private final Map<Class<?>, String> mappings;
private VerboseTypeAdapterFactory(final Map<Class<?>, String> mappings) {
this.mappings = mappings;
}
static TypeAdapterFactory get(final Map<Class<?>, String> mappings) {
// Create a defensive copy to make sure the map is not modified from outside
final Map<Class<?>, String> mappingsCopy = mappings
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return new VerboseTypeAdapterFactory(mappingsCopy);
}
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> typeToken) {
final Class<? super T> rawType = typeToken.getRawType();
// Not something we can handle?
if ( !mappings.containsKey(rawType) ) {
// Then let Gson do its job elsewhere
return null;
}
// Getting a property name we want to use for a particular class
final String propertyName = mappings.get(rawType);
// And getting the original type adapter for this class (effectively ReflectiveTypeAdapterFactory.Adapter)
final TypeAdapter<T> delegateTypeAdapter = gson.getDelegateAdapter(this, typeToken);
return VerboseTypeAdapter.get(propertyName, delegateTypeAdapter);
}
private static final class VerboseTypeAdapter<T>
extends TypeAdapter<T> {
private final String propertyName;
private final TypeAdapter<T> delegateTypeAdapter;
private VerboseTypeAdapter(final String propertyName, final TypeAdapter<T> delegateTypeAdapter) {
this.propertyName = propertyName;
this.delegateTypeAdapter = delegateTypeAdapter;
}
private static <T> TypeAdapter<T> get(final String propertyName, final TypeAdapter<T> delegateTypeAdapter) {
return new VerboseTypeAdapter<>(propertyName, delegateTypeAdapter)
// A convenient method to simplify null-handling
.nullSafe();
}
#Override
#SuppressWarnings("resource")
public void write(final JsonWriter out, final T object)
throws IOException {
// Open the object with `{`
out.beginObject();
// Prepend the object with its reserved name
out.name(propertyName);
// Write the object
delegateTypeAdapter.write(out, object);
// And close the object with `}`
out.endObject();
}
#Override
public T read(final JsonReader in)
throws IOException {
// Assume the very first token is `{`
in.beginObject();
// Peeking what's the actual property name
final String actualPropertyName = in.nextName();
// And if it's not we expect, throw a JSON parse exception
if ( !actualPropertyName.equals(propertyName) ) {
throw new JsonParseException("Expected " + propertyName + " but was " + actualPropertyName);
}
// Otherwise read the value led by the property name
final T object = delegateTypeAdapter.read(in);
// And make sure there are no more properties
if ( in.hasNext() ) {
throw new JsonParseException(propertyName + " is expected to be the only top-most property");
}
// Assume the very last token is `}` (this works for the check above, but we made it more semantical)
in.endObject();
return object;
}
}
}
So, for example, the following code
private static final Gson gson = new GsonBuilder()
.setFieldNamingStrategy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapterFactory(VerboseTypeAdapterFactory.get(ImmutableMap.of(UserDetails.class, "user")))
.create();
...
final UserDetails userDetails = gson.fromJson(jsonReader, UserDetails.class);
System.out.println(userDetails.name);
System.out.println(userDetails.email);
System.out.println(userDetails.timeZone);
final String json = gson.toJson(userDetails);
System.out.println(json);
produces
username
blah#blah.com
America/New_York
{"user":{"name":"username","email":"blah#blah.com","time_zone":"America/New_York"}}
As the conclusion:
No more excessive Gson instantiation.
Original Gson instance configuration inherited (i.e. FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES set once).
No intermediate JsonElement instances.

gson: Add function result to an object created by toJson()

gson is such a great serialize/deserialization tool. It's really simple to get a JSON representation of an arbitrary object by using the toJson-function.
Now I want to send the data of my object to the browser to be used within javascript/jQuery. Thus, I need one additional JSON element defining the dom class of the object which is coded within my object as a dynamic/memberless function
public String buildDomClass()
How to add this string to my String created by the toJson function?
Any ideas?
Thanks a lot
An easy way is to combine a TypeAdapterFactory and an interface.
First an interface for your method :
public interface MyInterface {
public String buildDomClass();
}
then the factory :
final class MyAdapter implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> tokenType) {
final TypeAdapter<T> adapter = gson.getDelegateAdapter(this, tokenType);
return new TypeAdapter<T>() {
#Override
public T read(JsonReader reader) throws IOException {
return adapter.read(reader);
}
#Override
public void write(JsonWriter writer, T value) throws IOException {
JsonElement tree = adapter.toJsonTree(value);
if (value instanceof MyInterface) {
String dom = ((MyInterface) value).buildDomClass();
JsonObject jo = (JsonObject) tree;
jo.addProperty("dom", dom );
}
gson.getAdapter(JsonElement.class).write(writer, tree);
}
};
}
}
Easy to understand, if the object you want to serialize implement the interface, you delegate the serializing, and then you add an extra property holding you DOM.
In case you don't know, you register a factory like this
Gson gson = new GsonBuilder().registerTypeAdapterFactory(new MyAdapter()).create();

Jackson Json Object mapper : how to map an array?

I am trying to map an array from a backend api call. How can I map this data knowing that :
the following classes will be used to hold the json array data :
#Data
private static class UserListBean {
private List<UserBean> userList;
}
#Data
private static class UserBean {
private String id;
private String userName;
private String password;
}
the json will have the following format (the following example just have one item in it) :
[
{
"id":1,
"userName":"bob",
"password":"403437d5c3f70b1329f37a9ecce02adbbf3e986"
}
]
I am using Jackson and I have tried the following so far but it keeps sending me back an exception
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
final ObjectReader reader = mapper.reader(UserListBean.class);
GeoHubAccountListBean accounts = null;
try {
accounts = reader.readValue(jsonString);
} catch (final IOException ex) {
log.error("Cannot convert JSON into a list of users", ex);
}
Here the final ObjectReader reader = mapper.reader(UserListBean.class); throws an exception
Can not deserialize instance of com.xxx.XXX$UserListBean out of START_ARRAY token
Any idea ?
thanks
Well, if you are trying to deserialize json to an object of type UserListBean, then you need to deserialize a JSONObject (Java Objects tend to map to JSONObjects).
Therefore, your outer most json construct should be an object. Your outer most json construct is a JSONArray.
Your UserListBean has a single field, which is a List<UserBean>. So your top level json construct (which is a JSONObject) should contain a single field with the name 'userList' with a value that is a JSONArray (Java Collections tend to map to JSONArrays).
I think this is the actual json you are looking for:
{
"userList":[
{
"id":1,
"userName":"bob",
"password":"403437d5c3f70b1329f37a9ecce02adbbf3e986"
}
]
}
If you have no control over the json coming in, then you probably want to ditch the parent object UserListBean and deal directly with the List<UserBean>, as that would work with the json you have provided.
Try something like the following:
public static <T> T mapFromJson(String json, Class<T> clazz) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return objectMapper.readValue(json, clazz);
}
public static void main(String[] args) throws IOException {
HeaVO HeaVO=new HearVO();
HeaVO.setId("Id");
List<HeaVO> listVO=new ArrayList<HeaVO>();
listVO.add(HeaVO);
List<HeaVO> listHeaVORes=Arrays.asList(mapFromJson(mapToJson(listHeaVO), HeaVO[].class));
System.out.println(((HeaVO)listVORes.get(0)).getId());
}