suppose I've got a collection of people defined like this in JSON.
{
"NOM": "Doe",
"PRENOM": "John",
"EMAIL": "john.doe#email.me",
"VILLE": "Somewhere",
"LIKE1": "Lolcats",
"LIKE2": "Loldogs",
"LIKE3": "Lolwut",
"HATE1": "Bad stuff",
"HATE2": "Bad bad stuff"
}
Is it possible to write a JsonDeserializer that will aggregate and transform LIKE* and HATE* fields into a collection of Liking, set as a property of Person? (Note that there are only LIKE1, LIKE2, LIKE3, HATE1, HATE2.)
The final result properties would be something like:
public class Person {
private final String lastName;
private final String firstName;
private final String email;
private final String town;
private final Collection<Liking> likings;
// c-tor, getters
}
I've already the logic that can deserialize a given LIKE*/HATE* property into a Liking object but I fail to understand to aggregate and add them to a Person liking attribute.
Thx in advance!
It would have been nice if you had some code that showed you began the process of solving this problem yourself. But, here is a sample custom deserializer that does pretty much what you're looking for:
class PersonDeserializer extends JsonDeserializer<Person> {
#Override
public Person deserialize(final JsonParser parser,
final DeserializationContext content) throws IOException,
JsonProcessingException {
final ObjectCodec codec = parser.getCodec();
final JsonNode node = codec.readTree(parser);
final Person person = new Person();
final Iterator<String> fieldNameIter = node.getFieldNames();
while (fieldNameIter.hasNext()) {
final String fieldName = fieldNameIter.next();
if (fieldName.equalsIgnoreCase("EMAIL")) {
person.setEmail(node.get(fieldName).getTextValue());
} else if (fieldName.equalsIgnoreCase("NOM")) {
person.setFirstName(node.get(fieldName).getTextValue());
} else if (fieldName.equalsIgnoreCase("PRENOM")) {
person.setLastName(node.get(fieldName).getTextValue());
} else if (fieldName.equalsIgnoreCase("VILLE")) {
person.setTown(node.get(fieldName).getTextValue());
} else if (fieldName.startsWith("LIKE")) {
person.addLike(Liking.LikingType.LIKE, node.get(fieldName)
.getTextValue());
} else if (fieldName.startsWith("HATE")) {
person.addLike(Liking.LikingType.HATE, node.get(fieldName)
.getTextValue());
}
}
return person;
}
}
It presumes a Liking object similar to this:
public class Liking {
public static enum LikingType {
LIKE, HATE;
}
private LikingType type;
private String value;
// Constructors, getters/setters
}
And some changes to your Person object which I think you can figure out. If you intend to serialize the object to JSON in the same custom format then you will have to write a corresponding JsonSerializer.
Another option, not quite as robust, would be too simply use a map to store the likes and dislikes exactly as is. This solution would omit any explicit mappings for likes/dislikes and utilize the #JsonAny annotation to capture them. In this scheme the Person object would look like this:
public class Person {
private String lastName;
private String firstName;
private String email;
private String town;
#JsonAny
private Map<String, Object> otherProperties;
// Constructors, getters/setters
}
Deserializing your JSON into this modified version of Person will place all unrecognized properties into the hash map, as key-value pairs.
I'm pretty sure you can't do it the way you intend, how about doing it like this:
{
"NOM": "Doe",
"PRENOM": "John",
"EMAIL": "john.doe#email.me",
"VILLE": "Somewhere",
"likings": ["Lolcats", "Loldogs", "LIKE3": "Lolwut", "Bad stuff", "Bad bad stuff" ]
}
Related
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.
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
I want to program a restful API and annotate my data with schema.org. For this I wanted to use Jackson-Jsonld. Annotating simple objects with jackson-jsonld were no problem, but complex ones with nested objects got me stucked. In my jsonld the simple attributes like id, name got anntotated but the nested location not.
I read about Serialization and that it should help in order to get the second object. However, after implementing my serialization part it seems that the serialization did not changed anything.
Here is my sample output, the type for location should be PostalAddress however the type is missing:
{"#context":
{"uri":"http://schema.org/url","name":"http://schema.org/name","location":"http://schema.org/location"},
"#type":"http://schema.org/Organization",
"uri":"http://localhost:8080/kangarooEvents/venue/12",
"name":"Joondalup Library - Ground Floor Meeting Room",
"location":{
"address":"102 Boas Avenue",
"city":"Joondalup",
"zip":"6027",
"country":"Australia",
"state":"WA"},
"#id":12}
I want to annotate an organization which has a single location:
#JsonldType("http://schema.org/Organization")
public class Venue {
#JsonldId
private Integer id;
#JsonldProperty("http://schema.org/url")
private String uri;
#JsonldProperty("http://schema.org/name")
private String name;
#JsonSerialize(using = CostumLocationSerializer.class)
#JsonldProperty("http://schema.org/location")
private Location location;
Location:
#JsonldType("http://schema.org/PostalAddress")
public class Location {
#JsonldProperty("http://schema.org/streetAddress")
private String address;
#JsonldProperty("http://schema.org/addressLocality")
private String city;
#JsonldProperty("http://schema.org/addressRegion")
private String state;
#JsonldProperty("http://schema.org/addressRegion")
private String country;
#JsonldProperty("http://schema.org/postalCode")
private String zipcode;
Serialization:
public class CostumLocationSerializer extends StdSerializer<Location> {
private ObjectMapper mapper = new ObjectMapper();
public CostumLocationSerializer(){
this( null);
}
protected CostumLocationSerializer(Class<Location> t) {
super(t);
}
#Override
public void serialize(Location location, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("address", location.getAddress());
jsonGenerator.writeStringField("city", location.getCity());
jsonGenerator.writeStringField("zip", location.getZipcode());
jsonGenerator.writeStringField("country", location.getCountry());
jsonGenerator.writeStringField("state", location.getState());
jsonGenerator.writeEndObject();
String serialized = mapper.writeValueAsString(location);
}
}
I think that my problem might be in the serialization but I can not figure it out. Maybe someone annotated nested obj. and can tell me what my problem is.
Just skip the jackson-jsonld part and do it manually
Create JSON - just introduce a field for type and id into your java classes.
Create a JSON-LD context - map your id and type fields in an additional #context object
Combine context and data - e.g. just add your #context object after your 'normal' json serialization using standard jackson API.
Example
#Test
public void createJsonFromPojo() throws Exception {
ObjectMapper mapper=new ObjectMapper();
// Create object structure
Venue venue = new Venue();
venue.location = new Location();
venue.id="12";
venue.uri="http://localhost:8080/kangarooEvents/venue/12";
venue.name="Joondalup Library - Ground Floor Meeting Room";
venue.location.address="102 Boas Avenue";
venue.location.city="Joondalup";
venue.location.state="WA";
venue.location.country="Australia";
venue.location.zipcode="6027";
//1. Create JSON
ObjectNode myData = mapper.valueToTree(venue);
//2. Create a JSON-LD context
ArrayNode context = mapper.createArrayNode();
context.add("http://schema.org/");
ObjectNode myContext=mapper.createObjectNode();
myContext.put("id", "#id");
myContext.put("type", "#type");
context.add(myContext);
//3. Combine context and data
myData.set("#context",context);
//4. Print
StringWriter w = new StringWriter();
mapper.configure(SerializationFeature.INDENT_OUTPUT, true).writeValue(w, myData);
String result= w.toString();
System.out.println(result);
}
public class Venue {
public final String type = "http://schema.org/Organization";
public String id;
public String uri;
public String name;
public Location location;
}
public class Location {
public final String type = "http://schema.org/PostalAddress";
public String address;
public String city;
public String state;
public String country;
public String zipcode;
}
Gives you
{
"#context": [
"http://schema.org/",
{
"id": "#id",
"type":"#type"
}
],
"uri":"http://localhost:8080/kangarooEvents/venue/12",
"name":"Joondalup Library - Ground Floor Meeting Room",
"location":{
"address":"102 Boas Avenue",
"city":"Joondalup",
"zip":"6027",
"country":"Australia",
"state":"WA",
"type":"http://schema.org/PostalAddress"
},
"id":"12",
"type":"http://schema.org/Organization"
}
View Example in Playground
I have a JSON array like as shown below which I need to serialize it to my class. I am using Jackson in my project.
[
{
"clientId": "111",
"clientName": "mask",
"clientKey": "abc1",
"clientValue": {}
},
{
"clientId": "111",
"clientName": "mask",
"clientKey": "abc2",
"clientValue": {}
}
]
In above JSON array, clientValue will have another JSON object in it. How can I serialize my above JSON array into my java class using Jackson?
public class DataRequest {
#JsonProperty("clientId")
private String clientId;
#JsonProperty("clientName")
private int clientName;
#JsonProperty("clientKey")
private String clientKey;
#JsonProperty("clientValue")
private Map<String, Object> clientValue;
//getters and setters
}
I have not used jackson before so I am not sure how can I use it to serialize my JSON array into Java objects? I am using jackson annotation here to serialize stuff but not sure what will be my next step?
You can create a utility function shown below. You may want to change the Deserialization feature based on your business needs. In my case, I did not want to fail on unknown properties => (FAIL_ON_UNKNOWN_PROPERTIES, false)
static <T> T mapJson(String body,
com.fasterxml.jackson.core.type.TypeReference<T> reference) {
T model = null;
if(body == null) {
return model;
}
com.fasterxml.jackson.databind.ObjectMapper mapper =
new com.fasterxml.jackson.databind.ObjectMapper();
mapper.configure(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
try {
model = mapper.readValue(body, reference);
} catch (IOException e) {
//TODO: log error and handle accordingly
}
return model;
}
You can call it using similar approach as shown below:
mapJson(clientValueJsonString,
new com.fasterxml.jackson.core.type.TypeReference<List<DataRequest>>(){});
You can try #JsonAnyGetter and #JsonAnySetter annotations with an inner class object. Also clientName should have type String, not int.
public class DataRequest {
private String clientId;
private String clientName;
private String clientKey;
private ClientValue clientValue;
//getters and setters
}
public class ClientValue {
private Map<String, String> properties;
#JsonAnySetter
public void add(String key, String value) {
properties.put(key, value);
}
#JsonAnyGetter
public Map<String,String> getProperties() {
return properties;
}
}
We have this Json:
{
"id": 500,
"field1": "TESTE",
"banco": {
"id": 300,
"descricao": "BANCO_TESTE"
},
"categorias": [
{
"id": 300,
"descricao": "PT",
"publica": true
}
]
}
And my beans:
public class Asking implements Serializable {
private long id;
private String field1;
private Bank bank;
private List<Categoria> categorias;
//[getters and setters]
}
The beans Bank and Categoria:
public class Bank implements Serializable {
private Long code;
private Long id;
private String descricao;
//getters and setters
}
public class Categoria implements Serializable {
private Long code;
private Long id;
private String descricao;
private boolean marcada;
private boolean publica;
//getters and setters
}
When I call:
gson.fromJson(strJson, tokenType);
The error appears:
Method threw 'java.lang.StackOverflowError' exception.
What is wrong?
I can't reproduce this problem. One of two things are wrong here:
Your beans are not defined as you say they are. Check to see if they have other fields hidden within the getter and setter method section. This can happen if you have a circular reference.
You've stated in the comments that this is likely to be your problem. I recommend:
Remove the extra fields from your bean
Create a new class that contains the extra fields, and a field for the Asking instance
Deserialize the Asking instance using Gson, and then pass it into the new class's constructor.
You are doing something unexpected with your setup of the gson.fromJson method. Here's what I'm using that works great:
public static void parseJSON(String jsonString) {
Gson gsonParser = new Gson();
Type collectionType = new TypeToken<Asking>(){}.getType();
Asking gsonResponse = gsonParser.fromJson(jsonString, collectionType);
System.out.println(gsonResponse);
}
Either check your bean class definitions for extra fields, or, failing that, try to make your deserialization match mine.