Jackson JSON Custom Serialization of Map with top level value - java

I need to serialize the following class:
#JsonSerialize(using = ItemSerializer.class)
public class Project {
#JsonProperty
public Map<String, Field> fields = new HashMap<String, Field>();
}
The Field class:
public class Field {
public String name;
#JsonIgnore
public boolean isStringField;
public Field(String name, boolean isStringField) {
this.name = name;
this.isStringField = isStringField;
}
}
Serialize function:
#Override
public void serialize(...) throws ... {
jgen.writeStartObject();
for(Map.Entry<String, Field> entry : value.fields.entrySet()){
if(entry.getValue().isStringField){
jgen.writeStringField(entry.getKey(), entry.getValue().name);
}else{
jgen.writeObjectField(entry.getKey(), entry.getValue());
}
}
jgen.writeEndObject();
JSON I'm getting is almost what I need:
{
"fieldValue4": {
"name": "field4"
},
"fieldValue3": {
"name": "field3"
},
"fieldValue2": {
"name": "field2"
},
"fieldValue1": "field1"
}
Except that parent element named fileds (variable name from Project class)is not in this JSON. How can I add it to the result JSON to get something like:
{
"fields": {
"fieldValue4": {
"name": "field4"
},
"fieldValue3": {
"name": "field3"
},
"fieldValue2": {
"name": "field2"
},
"fieldValue1": "field1"
}
}

Since you have already specified your own custom serializer, then it is just a matter of specifying gen.writeObjectFieldStart("fields");, before serializing your fields map

Related

Custom Gson serializer with fields that are unknown at runtime

I'm attempting to do custom Gson serialization to create a Json object to send to a service except their are some fields that are not known at runtime.
The json I wish to create should look something like this:
{
"type": "configuration/entityTypes/HCP",
"attributes": {
"FirstName": [
{
"type": "configuration/entityTypes/HCP/attributes/FirstName",
"value": "Michael"
}
]
},
"crosswalks": [
{
"type": "configuration/sources/AMA",
"value": "10000012"
}
]
}
I am able to successfully create this json using Gson, but the issue is that I have thousands of fields that could be under the attributes object, in this example there is only the FirstName but if I was doing a create there would be as many attributes as that person, place or thing had associated with them.
Because currently I am able to create this using Gson by having 4 different classes:
Type
Attributes
FirstName
Crosswalks
But I want to be able to have FirstName, LastName, MiddleName, etc. all underneath the attributes object without creating an individual java class for all of them. The json would look like this in that case:
{
"type": "configuration/entityTypes/HCP",
"attributes": {
"FirstName": [
{
"type": "configuration/entityTypes/HCP/attributes/FirstName",
"value": "Doe"
}
],
"LastName": [
{
"type": "configuration/entityTypes/HCP/attributes/LastName",
"value": "John"
}
],
"MiddleName": [
{
"type": "configuration/entityTypes/HCP/attributes/MiddleName",
"value": "Michael"
}
]
},
"crosswalks": [
{
"type": "configuration/sources/AMA",
"value": "10000012"
}
]
}
Is there a way to use Gson to create the attributes object without creating java objects for all of the different attributes I have?
You can use Map<String, Object> where Object will be an one-element-array. See, for example, below model:
class Attributes {
private Map<String, Object> attributes;
// getters, setters
}
class Type {
private final String type;
private final String value;
public Type(String type, String value) {
this.type = type;
this.value = value;
}
// getters
}
Now, let's build attributes manually:
import com.google.gson.Gson;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class GsonApp {
public static void main(String[] args) {
Map<String, Object> map = new HashMap<>();
map.put("FirstName", Collections.singletonList(new Type("url/FirstName", "Rick")));
map.put("LastName", Collections.singletonList(new Type("url/LastName", "Pickle")));
Attributes attributes = new Attributes();
attributes.setAttributes(map);
String json = new Gson().newBuilder().setPrettyPrinting().create().toJson(attributes);
System.out.println(json);
}
}
Above code prints:
{
"attributes": {
"FirstName": [
{
"type": "url/FirstName",
"value": "Rick"
}
],
"LastName": [
{
"type": "url/LastName",
"value": "Pickle"
}
]
}
}

How can I convert a JSON string of objects into an ArrayList using Gson?

So I have a JSON string of countries like this:
{
"details": [
{
"country_id": "1",
"name": "Afghanistan",
"regions": null
},
{
"country_id": "2",
"name": "Albania",
"regions": null
},
{
"country_id": "3",
"name": "Algeria",
"regions": null
},
... and so on
}
Now I want to have a method that tries to convert this into an ArrayList of countries.
public static ArrayList<GFSCountry> get() {
return new Gson().fromJson(countriesJson, new TypeToken<ArrayList<GFSCountry>>(){}.getType());
}
But I get an
Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path
As per request, here is my GFSCountry class:
#SerializedName("country_id")
#Expose
private String countryId;
#SerializedName("name")
#Expose
private String name;
#SerializedName("regions")
#Expose
private Object regions;
public String getCountryId() {
return countryId;
}
public void setCountryId(String countryId) {
this.countryId = countryId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Object getRegions() {
return regions;
}
public void setRegions(Object regions) {
this.regions = regions;
}
I know I should adjust something either from the JSON string or the method. Any help?
Since the list is nested in your JSON you will need a small mapping class that contains the list.
Try
static class GFSCountryList {
public List<GFSCountry> details;
}
public static List<GFSCountry> get() {
return new Gson().fromJson(countriesJson, GFSCountryList.class).details;
}
It seems to me that Gson is expecting your json to be like this
[
{
"country_id": "1",
"name": "Afghanistan",
"regions": null
},
{
"country_id": "2",
"name": "Albania",
"regions": null
},
{
"country_id": "3",
"name": "Algeria",
"regions": null
},
... and so on
]
But it encounters an object (marked by { })
Either you have the possibility to change your json format, or you create a class with a "details" attribut as a list to be compatible with the json's format.

I would like to iterate a big Json's properties and sub properties name with jackson

My final aim is to get all the properties name and its types from a local file path.
Example
{
"description": "Something",
"id": "abc.def.xyzjson#",
"type": "object",
"properties": {
"triggerTime": {
"type": "string",
"description": "Time of adjustment event",
"source": "ab.cd",
"pattern": "something",
"required": true
},
"customerId": {
"type": "string",
"description": "Something",
"source": "ef.gh",
"required": true
}, ..... many more properties
Under some properties, there are sub-properties and their Type.
I want final Output as-
triggerTime String
customerId String (also sub)
There are multiple solutions to your problem.
One would be what Sharon suggested with just iterating the big map.
Another solution however would be to create wrapper classes ( POJOs ) to work with, which also could provide other useful aspects depending on your scale and use case.
public class YourJsonObject {
private String description;
private string id;
private String object;
private JsonProperties properties;
public YourJsonObject() {
}
public JsonProperties getProperties(){
return properties;
}
public void setProperties(JsonProperties properties){
this.properties = properties;
}
public String getDescription(){
return description;
}
public void setDescription(String description) {
this.description= description;
}
public String getId(){
return id;
}
public void setId(String id){
this.id = id;
}
//and so on with the getter setter
}
//The class JsonProperties used in YourJsonObject
public class JsonProperties{
private TriggerTime triggertime;
private Customer customerId;
public JsonProperties() {
}
public TriggerTime getTriggertime(){
return triggertime;
}
//and so on
}
Then somewhere else you can just do this:
ObjectMapper mapper = new ObjectMapper(); // create once, reuse
yourJsonObject example = new yourJsonObject(); // have your POJO you want to save
mapper.writeValue(new File("result.json"), example);
To read you can just use:
ObjectMapper mapper = new ObjectMapper(); // create once, reuse
yourJsonObject value = mapper.readValue(new File("data.json"), yourJsonObject .class); // data.json is the json file you want to read. Else you can also pass a String into it with method overloading.
Both snippets are taken from my linked wiki article from jackson themselves.
Jackson should automatically be able to parse this POJO to an equivalent JSON if configured correctly. Note: Jackson has to be globally registered and has to know about it. Please read the wiki of what you use to know about it. Jackson in 5 Minutes
My advice is to load the json into Map<String, Object> and then to recursively iterate on the map and collect the data you require
Here is the entire solution for you
public class JsonProperties
{
#SuppressWarnings("unchecked")
public static void main(String[] args)
{
ObjectMapper mapper = new ObjectMapper();
try (InputStream is = new FileInputStream("C://Temp/xx.json")) {
Map<String, Object> map = mapper.readValue(is, Map.class);
List<Property> properties = getProperties((Map<String, Object>)map.get("properties"));
System.out.println(properties);
} catch (IOException e) {
e.printStackTrace();
}
}
#SuppressWarnings("unchecked")
public static List<Property> getProperties(Map<String, Object> propertiesMap) {
List<Property> propertiesList = new ArrayList<>();
// iterate on properties
for (Map.Entry<String, Object> propertyEntry : propertiesMap.entrySet()) {
Property property = new Property();
property.name = propertyEntry.getKey();
Map<String, Object> propertyAttrsMap = (Map<String, Object>)propertyEntry.getValue();
property.type = (String)propertyAttrsMap.get("type");
// recursively get sub properties
if (propertyAttrsMap.containsKey("properties")) {
property.subProperties = getProperties((Map<String, Object>)propertyAttrsMap.get("properties"));
}
propertiesList.add(property);
}
return propertiesList;
}
public static class Property {
public String name;
public String type;
public List<Property> subProperties = new ArrayList<>();
#Override
public String toString() {
return name + " " + type + " " + subProperties;
}
}
}
using this sample input
{
"description": "Something",
"id": "abc.def.xyzjson#",
"type": "object",
"properties": {
"triggerTime": {
"type": "string",
"description": "Time of adjustment event",
"source": "ab.cd",
"pattern": "something",
"required": true
},
"customerId": {
"type": "string",
"description": "Something",
"source": "ef.gh",
"required": true
},
"complex": {
"type": "string",
"description": "Something",
"source": "ef.gh",
"required": true,
"properties": {
"sub1": {
"type": "int",
"description": "sub1",
"source": "ab.cd",
"required": true
},
"sub2": {
"type": "short",
"description": "sub2",
"source": "ab.cd",
"required": true
}
}
}
}
}
produces the following output:
[triggerTime string [], customerId string [], complex string [sub1 int [], sub2 short []]]

JSON generic collection deserialization

I've such DTO classes written in Java:
public class AnswersDto {
private String uuid;
private Set<AnswerDto> answers;
}
public class AnswerDto<T> {
private String uuid;
private AnswerType type;
private T value;
}
class LocationAnswerDto extends AnswerDto<Location> {
}
class JobTitleAnswerDto extends AnswerDto<JobTitle> {
}
public enum AnswerType {
LOCATION,
JOB_TITLE,
}
class Location {
String text;
String placeId;
}
class JobTitle {
String id;
String name;
}
In my project there is Jackson library used for serialization and deserialization of JSONs.
How to configure AnswersDto (use special annotations) or AnswerDto (annotation as well) classes to be able to properly deserialize request with AnswersDto in its body, e.g.:
{
"uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
"answers": [
{
"uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
"type": "LOCATION",
"value": {
"text": "Dublin",
"placeId": "121"
}
},
{
"uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
"type": "JOB_TITLE",
"value": {
"id": "1",
"name": "Developer"
}
}
]
}
Unfortunately Jackson by default maps value of AnswerDto object to LinkedHashMap instead of object of proper (Location or JobTitle) class type.
Should I write custom JsonDeserializer<AnswerDto> or configuration by use of #JsonTypeInfo and #JsonSubTypes could be enough?
To properly deserialize request with just one AnswerDto in form of
{
"uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
"type": "LOCATION",
"value": {
"text": "Dublin",
"placeId": "121"
}
}
I'm using:
AnswerDto<Location> answerDto = objectMapper.readValue(jsonRequest, new TypeReference<AnswerDto<Location>>() {
});
without any other custom configuration.
I've resolved issue by using Jackson's custom annotations #JsonTypeInfo and #JsonSubTypes:
public class AnswerDto<T> {
private String uuid;
private AnswerType type;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Location.class, name = AnswerType.Types.LOCATION),
#JsonSubTypes.Type(value = JobTitle.class, name = AnswerType.Types.JOB_TITLE)
})
private T value;
}
My suggestion is to make a separate interface for possible answer values and use #JsonTypeInfo on it. You can also drop type field from AnswerDto, AnswerType enum, and additional *AnswerDto classes becuse jackson will add type info for you. Like this
public class AnswerDto<T extends AnswerValue> {
private String uuid;
private T value;
}
#JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY)
interface AnswerValue {}
class Location implements AnswerValue { /*..*/ }
class JobTitle implements AnswerValue { /*..*/ }
Resulting json will looks like this
{
"uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
"answers": [
{
"uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
"value": {
"#class": "com.demo.Location",
"text": "Dublin",
"placeId": "121"
}
},
{
"uuid": "e82544ac-1cc7-4dbb-bd1d-bdbfe33dee73",
"value": {
"#class": "com.demo.JobTitle",
"id": "1",
"name": "Developer"
}
}
]
}
Which will be parsed using
AnswersDto answersDto = objectMapper.readValue(json, AnswersDto.class);
But this solution applies only in cases when you are a producer of json data and you do not have to think about backward compatibility.
In other cases you'll have to make custom desetializer for AnswersDto class.

Jackson Serialize without field name

This is slight extension of my previous question. So based on dambros' answer, the json now looks like:
"Foo": {
"title": {
"type": "title",
"value": "...",
"variable": "..."
},
"message": {
"type": "message",
"value": "...",
"variable": "..."
}
}
But what I really want is:
"Foo": [
{
{
"type": "title",
"value": "...",
"variable": "..."
},
{
"type": "message",
"value": "...",
"variable": "..."
}
}
]
Is there any way to write the Foo field as an array and also not display the variable names as fields (i.e remove "title" :).
That is not valid JSON, however this is:
{
"Foo": [
{
"type": "title",
"value": "...",
"variable": "..."
},
{
"type": "message",
"value": "...",
"variable": "..."
}
]
}
This is a JSON object with a single field named Foo that is an array of objects.
You should read the JSON spec.
Alternatively, if you have a List<Foo>, you can serialize the list directly, giving you a JSON array as the root, instead of a JSON object as the root:
[
{
"type": "title",
"value": "...",
"variable": "..."
},
{
"type": "message",
"value": "...",
"variable": "..."
}
]
It seems that what you're trying to accomplish is to represent your java object in a way that you can send the object type and fields. Under that assumption, I'd try to get away from manual serialization. Just create a DTO with the format that you need, that you can populate with the domain objects you have. This would be an example:
public class FooSerialization {
public static class Foo {
private String title;
private String message;
}
public static class Foo2 {
private String value;
private String variable;
}
public static class ClassDTO {
private String type;
private List<FieldDTO> fields;
}
public static class FieldDTO {
private String type;
private String value;
private String fieldName;
}
public static void main(String[] args) throws JsonProcessingException {
Foo2 foo2 = new Foo2();
foo2.setValue("valueMessage");
foo2.setVariable("variableMessage");
Foo foo = new Foo();
foo.setMessage("messageMessage");
foo.setTitle("titleMessage");
ObjectMapper mapper = new ObjectMapper();
List<ClassDTO> dtos = new ArrayList<ClassDTO>();
dtos.add(convert(foo));
dtos.add(convert(foo));
System.out.println(mapper.writeValueAsString(dtos));
}
private static ClassDTO convert(Object obj) {
ClassDTO dto = new ClassDTO();
dto.setType(obj.getClass().getSimpleName());
List<FieldDTO> fieldDTOs = new ArrayList<FieldDTO>();
dto.setFields(fieldDTOs);
for (Field field : obj.getClass().getDeclaredFields()) {
field.setAccessible(true);
FieldDTO fieldDto = new FieldDTO();
try {
fieldDto.setFieldName(field.getName());
fieldDto.setValue(field.get(obj).toString());
fieldDto.setType(field.getType().getSimpleName());
fieldDTOs.add(fieldDto);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return dto;
}
}
Getters and Setters are omitted for simplicity. This basically converts from Foo or Foo2 to certain ClassDTO, that contains type and a list of FieldDTOs that have the field details.
Output looks like this:
[
{
"type": "Foo",
"fields": [
{
"fieldName": "title",
"type": "String",
"value": "titleMessage"
},
{
"fieldName": "message",
"type": "String",
"value": "messageMessage"
}
]
},
{
"type": "Foo2",
"fields": [
{
"fieldName": "value",
"type": "String",
"value": "valueMessage"
},
{
"fieldName": "variable",
"type": "String",
"value": "variableMessage"
}
]
}
]
It looks to me that you can solve lots of problems if you can use something like this:
#JsonFormat(shape=JsonFormat.Shape.ARRAY)
public static class Foo {
#JsonProperty public Foo1 title;
#JsonProperty public Foo2 message;
}
#JsonTypeInfo(use= JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
#JsonSubTypes({#JsonSubTypes.Type(value = Foo1.class, name = "title"),
#JsonSubTypes.Type(value = Foo2.class, name = "message")})
public static class FooParent{
#JsonProperty private String value;
#JsonProperty private String variable;
}
public static class Foo1 extends FooParent{}
public static class Foo2 extends FooParent{}
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Foo foo = new Foo();
foo.title = new Foo1();
foo.message = new Foo2();
String serialized = mapper.writeValueAsString(foo);
System.out.println(serialized);
}
Result is:
[
{"type":"title","value":null,"variable":null},
{"type":"message","value":null,"variable":null}
]
Read following blog json in java
This post is a little bit old but still i want to answer you Question
Step 1: Create a pojo class of your data.
Step 2: now create a object using json.
Foo foo = null;
ObjectMapper mapper = new ObjectMapper();
try{
foo = mapper.readValue(newFile("/home/sumit/foo.json"),Foo.class);
} catch (JsonGenerationException e){
e.printStackTrace();
}
For further reference you can refer following link
Thanks

Categories