JSON Object representation - java

I am new to JSON. so please help me....
I am trying to write a Rest service that returns a Person object as follows:
PersonJSONAdapter implements JsonSerializer<Person>, JsonDeserializer<Person>
{
public JsonElement serialize(final Person src, final Type typeOfSrc, final JsonSerializationContext context)
{
if (src == null)
return null;
final JsonObject retVal = new JsonObject();
retVal.addProperty("firstname", src.getFirstname());
retVal.addProperty("lastname", src.getLastname());
return retVal;
}
so with this the output of the JSON looks like:
{ "firstname" : "XXX",
"lastname" : "YYY"
}
How can I make this one look like
“Person”
{
“firstname”: “XXX”,
"lastname":"YYY"
}
Thanks for looking...

That's not valid JSON. JSON is only a data representation and has no knowledge of meta data such as type information.
The closest you can get is by explicitly adding the type as an extra property, such as:
{ "#type":"Person", "firstname":"XXX", "lastname":"YYY" }
This means however that you'll have to select the right class when you deserialize it. You might want to check out some libraries, but I think they either require you to specify the class yourself or to add custom annotations to serializable classes.

Related

Removing Custom Serialisation for JSON objects

I currently have a custom serialiser. Purpose of my custom serialiser is to format my JSON object from this: {"id":["21","22", 23"]} to this {"id":"21,22,23"}.
My current implementation of my custom serialiser:
public static class ListSerialiser implements JsonSerializer<List<Member>> {
#Override
public JsonElement serialize(List<Member> src, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
List<String> memberId = new ArrayList<>(src.size());
for (Member member : src) {
memberId.add("" + Member.getId());
}
String memberIdAsString = TextUtils.join(",", memberId);
object.addProperty("[%s]", memberIdAsString);
return object;
}
}
Although my implementation got me what I wanted, just out of curiosity, I was wondering if there's a way to serialise without having to use Text Utils or string formatters to achieve this outcome: {"id":"21,22,23"}
There are a lot of ways to join strings, you can see most of them here
My favourite is:
String memberIdAsString = memberId.stream().collect(Collectors.joining(","));

How do I treat empty Strings as null objects with GSON?

I'm retrieving comments from the Reddit API. The model is threaded such that each Comment can internally have a List of Comments, named replies. Here's an example of how a JSON response would look:
[
{
"kind":"Listing",
"data":{
"children":[
{
"data":{
"body":"comment",
"replies":{
"kind":"Listing",
"data":{
"children":[
{
"data":{
"body":"reply to comment",
"replies":""
}
}
]
}
}
}
}
]
}
}
]
Here is how I model this with POJOs. The response above would be considered a List of CommentListings.
public class CommentListing {
#SerializedName("data")
private CommentListingData data;
}
public final class CommentListingData {
#SerializedName("children")
private List<Comment> comments;
}
public class Comment {
#SerializedName("data")
private CommentData data;
}
public class CommentData {
#SerializedName("body")
private String body;
#SerializedName("replies")
private CommentListing replies;
}
Note how the bottom level CommentData POJO refers to another CommentListing called "replies".
This model works until GSON reaches the last child CommentData where there are no replies. Rather than providing a null, the API is providing an empty String. Naturally, this causes a GSON exception where it expects an object but finds a String:
"replies":""
Expected BEGIN_OBJECT but was STRING
I attempted to create a custom deserializer on the CommentData class, but due to the recursive nature of the model it seems not to reach the bottom levels of the model. I imagine this is because I'm using a separate GSON instance to complete deserialization.
#Singleton
#Provides
Gson provideGson() {
Gson gson = new Gson();
return new GsonBuilder()
.registerTypeAdapter(CommentData.class, new JsonDeserializer<CommentData>() {
#Override
public CommentData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject commentDataJsonObj = json.getAsJsonObject();
JsonElement repliesJsonObj = commentDataJsonObj.get("replies");
if (repliesJsonObj != null && repliesJsonObj.isJsonPrimitive()) {
commentDataJsonObj.remove("replies");
}
return gson.fromJson(commentDataJsonObj, CommentData.class);
}
})
.serializeNulls()
.create();
}
How can I force GSON to return a null instead of a String so that it doesn't try to force a String into my POJO? Or if that's not possible, manually reconcile the data issue? Please let me know if you need additional context or information. Thanks.
In general your code looks good, but I would recommend a few things:
Your type adapters should not capture Gson instances from outside. Type adapter factories (TypeAdapterFactory) are designed for this purpose. Also, in JSON serializers and deserializers you can implicitly refer it through JsonSerializationContext and JsonDeserializationContext respectively (this avoids infinite recursion in some cases).
Avoid modification JSON objects in memory as much as possible: serializers and deserializers are just a sort of pipes and should not bring you surprises with modified objects.
You can implement a generic "empty string as a null" type deserializer and annotate each "bad" field that requires this kind of deserialization strategy. You might consider it's tedious, but it gives you total control wherever you need it (I don't know if Reddit API has some more quirks like this).
public final class EmptyStringAsNullTypeAdapter<T>
implements JsonDeserializer<T> {
// Let Gson instantiate it itself
private EmptyStringAsNullTypeAdapter() {
}
#Override
public T deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
if ( jsonElement.isJsonPrimitive() ) {
final JsonPrimitive jsonPrimitive = jsonElement.getAsJsonPrimitive();
if ( jsonPrimitive.isString() && jsonPrimitive.getAsString().isEmpty() ) {
return null;
}
}
return context.deserialize(jsonElement, type);
}
}
And then just annotate the replies field:
#SerializedName("replies")
#JsonAdapter(EmptyStringAsNullTypeAdapter.class)
private CommentListing replies;

GSON Serialize Polymorphic Object with Type Stored in a Different Object

To start off, I have looked at a few other answers for similar questions, but they do not answer my particular situation.
I'm parsing JSON messages which consist of a body and a header, where the header stores what type of object the body is:
{
"body": {
"eventName": "someEventName"
},
"header": {
"purpose": "event"
}
}
In Java, I've modeled this structure using the following classes:
public class Message {
public Body body;
public Header header;
}
public class Header {
public String purpose; // Marks what child class the body of the message uses
}
public abstract class Body {
// Child classes store additional fields
}
// Example implementation of the body class
public class EventBody extends Body {
public String eventName; // Name of some event
}
After doing some research, I found that RuntimeTypeAdapterFactory is normally used to parse/write polymorphic objects; however, the RutimeTypeAdapterFactory class relies on the type being stored in the base class of the polymorphic object (i.e. Body). But in this scenario, that's not the case ― the type is stored in another object, Header.
What would be the best way to go about parsing these kind of objects? I'd like to avoid having to write a custom Serializer/Deserializer for compactness, but I wouldn't mind writing them if it's necessary.
I realize that asking for a solution that doesn't involve a custom Serializer/Deserializer is a bit ridiculous, as this is exactly the type of scenario they'd be used in (I was thinking I could get away with a custom TypeAdapterFactory, but using a Serializer/Deserializer is easier).
Anyway, for my scenario, a combination of a custom Serializer/Deserializer for the Message class seems to work fine. Since I already use an enum to track different message purposes and their string names, I decided to simply add an additional field to that enum to store the corresponding body class.
MessagePurpose Enum:
public enum MessagePurpose {
EVENT("event", EventBody.class);
public final String purposeName;
public final Class bodyClass;
MessagePurpose(String purposeName, Class classi) {
this.purposeName = purposeName;
bodyClass = classi;
}
}
MessageSerializer:
public class MessageSerializer implements JsonSerializer<Message> {
#Override
public JsonElement serialize(Message message, Type type, JsonSerializationContext jsc) {
if(message == null) {
return null;
}
JsonObject messageObj = new JsonObject();
// Get the class representing the body object from the purpose enum
Class bodyClassType = message.getPurpose().bodyClass;
messageObj.add("body", jsc.serialize(message.getBody(), bodyClassType));
messageObj.add("header", jsc.serialize(message.getHeader(), Header.class));
return messageObj;
}
}
MessageDeserializer:
public class MessageDeserializer implements JsonDeserializer<Message> {
#Override
public Message deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException {
Header header = jdc.deserialize(je.getAsJsonObject().get("header"), Header.class);
// Get the class representing the body object from the purpose enum
Class bodyClassType = header.getPurpose().bodyClass;
Body body = jdc.deserialize(je.getAsJsonObject().get("body"), bodyClassType);
return new Message(body, header);
}
}
Main function to test with:
public static void main(String[] args) {
GsonBuilder gb = new GsonBuilder();
// Register the Message class since I need to access info in the header
gb.registerTypeAdapter(Message.class, new MessageDeserializer());
gb.registerTypeAdapter(Message.class, new MessageSerializer());
Gson gson = gb.setPrettyPrinting().create();
EventBody event = new EventBody(EventType.SOME_EVENT_NAME);
String eventJson = gson.toJson(event.getAsMessage());
System.out.println(eventJson);
Message newEvent = gson.fromJson(eventJson);
System.out.println("\nEvent type: " + ((EventBody) newEvent.getBody()).getEventName());
}
The above test class prints:
{
"body": {
"eventType": "someEventName"
},
"header": {
"purpose": "event"
}
}
Event Type: someEventName
This output matches the JSON of the Messages I'm parsing, and it seems to deserialize different types of messages just fine.

Deserializing json from retrofit using jackson where same variable name can represent two different objects

The response from retrofit2 may be of the following types.(and we don't know before hand which response will come)
{
"id": "abc",
"place": "LA",
"driverId": "abbabaaan"
}
or
{
"id": "abc",
"place": "LA",
"driverId": {
"name": "xyz",
"id": "jygsdsah",
"car": "merc"
}
}
Is there any way to define a class so that while deserializing jackson will check the type of object "driverId" contains and assigns it to say "driverIdObj" field or "driverIdStr" field in the class.
You could deserialize to a Map. Afterwards, you could inspect the map and decide to which of the 2 types you convert the map. Take a look at this answer: Deserializing JSON based on object type
To convert from Map to Object you can use ObjectMapper::convertValue, e.g
mapper.convertValue(map, Response1.class)
You can check whether the json has values inside it;
String jsonString= "{ ... }";
Object json = new JSONTokener(jsonString).nextValue();
if (json instanceof JSONObject){
//do operations related with object
}
else if (json instanceof JSONArray) {
//do operations based on an array
}
Try this
JSONObject jsonObject = new JSONObject("your Response String");
Object obj = jsonObject.get("driverId"); //handle Exceptions
if (obj instanceof String){
//do String stuff
}
else if (obj instanceof JSONObject) {
//do json object stuff
}
Make some special handling for the driverId field in your response class using the JsonNode class. Something like the following:
public class Response {
private String id, place, driverIdStr;
private DriverIdObj driverIdObj;
// ... Various getters and setters omitted.
public void setDriverId(JsonNode driverId) {
if (driverId.isObject()) {
// Process the complex version of DriverId.
driverIdObj = new DriverIdObj( /* retrieve fields from JsonNode */ );
} else {
// Process the simple version of DriverId
driverIdStr = driverId.asText();
}
}
}
This lets you maintain a normal approach for most of the response, while making it possible to handle the special field with a minimum of pain.

Android : parsing large json response

I am using google gson-2.2.1 library for parsing large response of JSON.
I have to parse a JSON response where structure may vary.
First case, when the response contains more than one team:
"Details":{
"Role":"abc",
"Team":[
{
"active":"yes",
"primary":"yes",
"content":"abc"
},
{
"active":"yes",
"primary":"yes",
"content":"xyz"
}
],
Second case, when only one team is passed:
"Details":{
"Role":"abc",
"Team":
{
"active":"yes",
"primary":"yes",
"content":"abc"
}
}
There are my base classes used for parsing:
class Details {
public String Role;
public ArrayList<PlayerTeams> Team = new ArrayList<PlayerTeams>();
PlayerTeams Team; // when JsonObject
}
class PlayerTeams {
public String active;
public String primary;
public String content;
}
The problem is that I can not use ArrayList<PlayerTeams> when I have only one of them and it's returned as JsonObject.
Gson can identify static format of JSON response. I can trace full response dynamically by checking if "Team" key is instance of JsonArray or JsonObject but it would be great if a better solution is available for that.
Edit :
If my response is more dynamic..
"Details":{
"Role":"abc",
"Team":
{
"active":"yes",
"primary":"yes",
"content":"abc"
"Test":
{
"key1":"value1",
"key2":"value2",
"key3":"value3"
}
}
}
In my edited question, I am facing problem while my response is more dynamic..Team and Test can be JsonArray or JsonObject.. It really harassing me because sometime Test object may array when more data, may object when single data, string when no data. There is no consistency in response.
You need a type adapter. This adapter would be able to distinguish which format is coming and instance the right object with the right values.
You can do this by:
implement your own type adapter by creating a class that implements JsonSerializer<List<Team>>, JsonDeserializer<List<Team>>, of course JsonSerializer is just needed in case you need to serialize it in that matter too.
Register the type adapter to you GsonBuilder like: new GsonBuilder().registerTypeAdapter(new TypeToken<List<Team>>() {}.getType(), new CoupleAdapter()).create()
The deserialize method could look like:
public List<Team> deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws com.google.gson.JsonParseException {
if (json.isJsonObject()) {
return Collections.singleton(context.deserialize(json, Team.class));
} else {
return context.deserialize(json, typeOfT);
}
}

Categories