Jackson Deserialize array or single object [duplicate] - java

I'm new with java and objectMapper. I'm trying to parse json field that is possible that a key have two types, it could be a string or array.
examples:
{
"addresses": [],
"full_name": [
"test name_1",
"test name_2"
],
}
or
{
{
"addresses": [],
"full_name": "test name_3",
}
}
Class example:
#JsonIgnoreProperties(ignoreUnknown = true)
#Data -> lombok.Data
public class Document {
private List<String> addresses;
#JsonProperty("full_name")
private String fullName;
}
I used objectMapper to deserialize json, works correctly when the 'full_name' field has a string but when arrive an array fail deserialization.
The idea is that when arrive a string put value in attribute but when arrive array, concatenate de array elements as string (String.join(",", value))
It's possible to apply custom deserialization in a class method? For example setFullName() (use lombok.Data)
I saw others examples in this site, but not work.
Thank's for all

From jackson 2.6 you can use JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY
#JsonProperty("full_name")
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private String[] fullName;

Elaborating on #Deadpool answer, you can use setter which accept the array and then join it to string:
#JsonProperty("full_name")
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
void setFullName(String[] name)
{
this.fullName = String.join(",", name);
}

Both answers are great. I just want to mention about custom Deserializer.
You can easily extend from StdDeserializer<Document> and override deserialize method:
public class DocumentDeserializer extends StdDeserializer<Document> {
#Override
public Document deserialize(JsonParser p, DeserializationContext ctxt, Document value) throws IOException {
JsonNode root = p.getCodec().readTree(p);
JsonNode node = root.get("full_name");
if(node.isArray()) {
//get array data from node iterator then join as String and
//call setFirstName
}
return value;
}
}
Then don't forget to call registerModule of ObjectMapper to register your deserializer

Related

JSON to Java object with Random Key

I am trying to convert following JSON to Java object and ending up with UnrecognizedPropertyException.
{
"5214": [{
"name": "sdsds",
"age": "25",
"address": null
},
{
"name": "sdfds",
"age": "26",
"address": null
}]
}
Here "5214" is the random key that I get. I can covert it by modifying JSON little bit. But I want to know whether any possible way to convert the mentioned JSON. I even tried with following snippet taking some reference.
public class SampleTest {
private Map<String, List<EmployeeDetails>> employeeDetails = new HashMap<String, List<EmployeeDetails>>();
public Map<String, List<EmployeeDetails>> getEmployeeDetails() {
return employeeDetails;
}
public void setEmployeeDetails(Map<String, List<EmployeeDetails>> employeeDetails) {
this.employeeDetails = employeeDetails;
}
}
public class EmployeeDetails {
private String name;
private String age;
private String address;
//Getters and Setters
}
Can someone guide me on this?
Use Type Reference (Import Jackson Package for Java)
TypeReference<Map<String, List<EmployeeDetails>>> typeReference = new TypeReference<Map<String, List<EmployeeDetails>>>()
{
};
Map<String, List<EmployeeDetails>> employeeDetails = new ObjectMapper().readValue(jsonString, typeReference);
Check something from that
Maybe:
public class Data {
// String contain the Key, for example: 5214
Map<String, List<EmployeeDetails>> employeeDetails =
new HashMap<String,List<EmployeeDetails>>();
public Data() {
}
#JsonAnyGetter
public Map<String, List<EmployeeDetails>> getEmployeeDetails() {
return employeeDetails;
}
}
I would use custom deserializer with few helper classes. To make the code (matter of opinion I guess) clearer, create the list object:
#SuppressWarnings("serial")
#Getter #Setter
public class EmployeeDetailsList extends ArrayList<EmployeeDetails> {
// this will hold the arbitrary name of list. like 5214
private String name;
}
Then this list seems to be inside an object, say Wrapper:
#Getter
#RequiredArgsConstructor
#JsonDeserialize(using = WrapperDeserializer.class)
public class Wrapper {
private final EmployeeDetailsList employeeDetailsList;
}
So there is annotation #JsonDeserializer that handles deserializing Wrapper. It is not possible to directly deserialize unknown field names to some defined type so we need to use mechanism like this custom deserializer that inspects what is inside Wrapper and determines what to deserialize and how.
And here is how the deserializer works:
public class WrapperDeserializer extends JsonDeserializer<Wrapper> {
private final ObjectMapper om = new ObjectMapper();
#Override
public Wrapper deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
TreeNode node = p.readValueAsTree();
// This is the place for caution. You should somehow know what is the correct node
// Here I happily assume there is just the one and first
String fName = node.fieldNames().next();
EmployeeDetailsList edl = om.readValue(node.get(fName).toString(),
EmployeeDetailsList.class);
edl.setName(fName);
return new Wrapper(edl);
}
}
Please check it carefully it is not perfect in sense finding alwasy the correct node and maybe the instantiation can be done in other ways better. But it shoudl give you a hunch how it could be done.

How to parse field that may be a string and may be an array with Jackson

I'm new with java and objectMapper. I'm trying to parse json field that is possible that a key have two types, it could be a string or array.
examples:
{
"addresses": [],
"full_name": [
"test name_1",
"test name_2"
],
}
or
{
{
"addresses": [],
"full_name": "test name_3",
}
}
Class example:
#JsonIgnoreProperties(ignoreUnknown = true)
#Data -> lombok.Data
public class Document {
private List<String> addresses;
#JsonProperty("full_name")
private String fullName;
}
I used objectMapper to deserialize json, works correctly when the 'full_name' field has a string but when arrive an array fail deserialization.
The idea is that when arrive a string put value in attribute but when arrive array, concatenate de array elements as string (String.join(",", value))
It's possible to apply custom deserialization in a class method? For example setFullName() (use lombok.Data)
I saw others examples in this site, but not work.
Thank's for all
From jackson 2.6 you can use JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY
#JsonProperty("full_name")
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private String[] fullName;
Elaborating on #Deadpool answer, you can use setter which accept the array and then join it to string:
#JsonProperty("full_name")
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
void setFullName(String[] name)
{
this.fullName = String.join(",", name);
}
Both answers are great. I just want to mention about custom Deserializer.
You can easily extend from StdDeserializer<Document> and override deserialize method:
public class DocumentDeserializer extends StdDeserializer<Document> {
#Override
public Document deserialize(JsonParser p, DeserializationContext ctxt, Document value) throws IOException {
JsonNode root = p.getCodec().readTree(p);
JsonNode node = root.get("full_name");
if(node.isArray()) {
//get array data from node iterator then join as String and
//call setFirstName
}
return value;
}
}
Then don't forget to call registerModule of ObjectMapper to register your deserializer

Decide class for Jackson deserialisation based on JSON value of a specific key [duplicate]

I want to write json deserializer on class Type so that when Type is deserialized from given json based on name it maps value (of type interface Being) to its current implementation based on some factory method that returns correct class name based on name, and populates remaining class without any explicit deserialization and without creating object of TigerBeing or HumanBeing explicitly using new.
I tried to use #jsonCreator but there i have to initialize entire HumanBeing or TigerBeing using new and passing all json in constructor. I need auto mapping for types further used as further pojo can be quite complex.
{type:[{
"name": "Human",
"value": {
"height":6,
"weight":100,
"languages":["spanish","english"]
}
},
{
"name":"Tiger",
"value":{
"extinct":1,
"found":["Asia", "America", "Europe", "Africa"]
}
}
]}
I have:
public class Type {
String name;
Being value;
}
public interface Being {
}
public class TigerBeing implements Being {
Integer extinct;
String[] found;
}
public class HumanBeing implement Being {
Integer height;
Integer weight;
String[] languages;
}
import java.io.IOException;
public class BeingDeserializer extends JsonDeserializer<Being> {
#Override
public Expertise deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonMappingException {
JsonNode node = jp.getCodec().readTree(jp);
String beingName = node.get("name").asText();
JsonNode valueNode = node.get("value");
BeingName beingByName = ExpertiseName.getBeingByName(beingName);
if(beingByName ==null) {
throw new JsonMappingException("Invalid Being " + beingName);
}
Being being = JsonUtils.getObjectFromJsonNode(valueNode,
ExpertiseFactory.getExpertise(beingByName).getClass());
return being;
}
}
In this way I was able to solve the above problem.

GSON - print all fields that are annotated as deserialized=true

I have a Java EE project that is using GSON library (Google's library for processing of JSON objects).
In my entity classes I use #Expose annotation to control which fields are considered by GSON. I also use serialize/deserialize properties on that annotation to control which fields are considered when serializing a Java object to JSON and which fields are considered when deserializing JSON objects to Java objects. For example:
public class Movie {
#Expose(serialize=true, deserialize=false)
#Id
#GeneratedValue
private long id;
#Expose(serialize=true, deserialize=true)
private String name;
#Expose(serialize=true, deserialize=true)
private String genre;
#Expose(serialize=false, deserialize=true)
private String secretID;
}
Here when I send the JSON object to be deserialized into Java object I send an object like this:
{
"name": "Memento",
"genre": "thriller",
"secretID": "123asd"
}
And, when I serialize Java object to JSON I get something like this:
{
"id": 1,
"name": "Memento",
"genre": "thriller"
}
I have this Java code:
public static void main(String[] args) {
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setPrettyPrinting().create();
String json = gson.toJson(new Movie());
System.out.println(json);
}
that generates this as it's output:
{
"id": 0,
"name": "",
"genre": ""
}
Those are fields that are marked to be serialized. However, what if I need to print out all of the fields that are marked to be deserialized, so that I can easier create a JSON object that will be used as input when creating new Movies.
The desired output is this:
{
"name": "",
"genre": "",
"secretID": ""
}
Note: I don't want to change serialize/deserialize properties on #Expose annotations because they are set to how my application needs to work. I just need an easy way to generate a template JSON objects that will be used as input to my application, so I don't have to type it manually.
You could implement more generic ExclusionStrategy like:
#RequiredArgsConstructor
public class IncludeListedFields implements ExclusionStrategy {
#NonNull
private Set<String> fieldsToInclude;
#Override
public boolean shouldSkipField(FieldAttributes f) {
return ! fieldsToInclude.contains(f.getName());
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}
then use it like:
Set<String> fieldsToInclude =
new HashSet<>(Arrays.asList("name", "genre", "secretID"));
ExclusionStrategy es = new IncludeListedFields(fieldsToInclude);
Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls()
.addSerializationExclusionStrategy(es).create();
Note following things:
You should not now use the builder method .excludeFieldsWithoutExposeAnnotation.
By default Gson does not serialize fileds with null values so you need to use builder method .serializeNulls(). This does not generate Json with string values "" but just null.
In your example Json fields contained empty strings as values but you did not introduce default constructor Movie() that would initialize field values to empty strings so they remain null. But if you initialize them - say to empty string ""- then they are not null & you do not need to use builder method .serializeNulls().
BUT if you really need and want only to serialize based on #Expose(deserialize=true) then the ExclusionStrategy can be just:
public class PrintDeserializeTrue implements ExclusionStrategy {
#Override
public boolean shouldSkipField(FieldAttributes f) {
Expose annotationExpose = f.getAnnotation(Expose.class);
if(null != annotationExpose) {
if(annotationExpose.deserialize())
return false;
}
return true;
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}

how to write jackson deserializer based on property in json

I want to write json deserializer on class Type so that when Type is deserialized from given json based on name it maps value (of type interface Being) to its current implementation based on some factory method that returns correct class name based on name, and populates remaining class without any explicit deserialization and without creating object of TigerBeing or HumanBeing explicitly using new.
I tried to use #jsonCreator but there i have to initialize entire HumanBeing or TigerBeing using new and passing all json in constructor. I need auto mapping for types further used as further pojo can be quite complex.
{type:[{
"name": "Human",
"value": {
"height":6,
"weight":100,
"languages":["spanish","english"]
}
},
{
"name":"Tiger",
"value":{
"extinct":1,
"found":["Asia", "America", "Europe", "Africa"]
}
}
]}
I have:
public class Type {
String name;
Being value;
}
public interface Being {
}
public class TigerBeing implements Being {
Integer extinct;
String[] found;
}
public class HumanBeing implement Being {
Integer height;
Integer weight;
String[] languages;
}
import java.io.IOException;
public class BeingDeserializer extends JsonDeserializer<Being> {
#Override
public Expertise deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonMappingException {
JsonNode node = jp.getCodec().readTree(jp);
String beingName = node.get("name").asText();
JsonNode valueNode = node.get("value");
BeingName beingByName = ExpertiseName.getBeingByName(beingName);
if(beingByName ==null) {
throw new JsonMappingException("Invalid Being " + beingName);
}
Being being = JsonUtils.getObjectFromJsonNode(valueNode,
ExpertiseFactory.getExpertise(beingByName).getClass());
return being;
}
}
In this way I was able to solve the above problem.

Categories

Resources