I have an enum that looks something like this
public enum Example {
EXAMPLE_1,
EXAMPLE_2,
EXAMPLE_3,
}
I am trying to parse a json string like this:
String json = "{\"blah\": \"Example.EXAMPLE_1\"}"
I have tried defining a class like this:
public class Blah {
Example blah;
}
and using
gson.fromJson(json, Blah.class)
but it just sets the field to null. Is there anyway to do this? I unfortunately can't control the format of the json string so I have to parse it as is.
Default gson will parse enum field without class name. You can customise your own json deserializer for Example enum.
JsonDeserializer<?> jd = new JsonDeserializer<Example>() {
#Override
public Example deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
String enumStr = json.getAsString();
String enumVal = enumStr.split("\\."); // etc...
Example val = ... ...
//...
return val;
}
};
Gson gson = new GsonBuilder().registerTypeAdapter(Example.class, jd).create();
Try this json
String json = "{\"blah\": \"EXAMPLE_1\"}"
Related
I have a JSON string that is parsed using the GSON library into a Map like so:
static Type type = new TypeToken<Map<String, String>>() {}.getType();
// note the trailing/leading white spaces
String data = "{'employee.name':'Bob ','employee.country':' Spain '}";
Map<String, String> parsedData = gson.fromJson(data, type);
The problem I have is, my JSON attribute values have trailing/leading whitespaces that needs to be trimmed. Ideally, I want this to be done when the data is parsed to the Map using GSON. Is something like this possible?
You need to implement custom com.google.gson.JsonDeserializer deserializer which trims String values:
class StringTrimJsonDeserializer implements JsonDeserializer<String> {
#Override
public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
final String value = json.getAsString();
return value == null ? null : value.trim();
}
}
And, you need to register it:
Gson gson = new GsonBuilder()
.registerTypeAdapter(String.class, new StringTrimJsonDeserializer())
.create();
I'm trying to deserialize the following structure
{ meta: { keywords: [a, b, c, d]} ... }
other valid structures are
{ meta: { keywords: "a,b,c,d"} ... }
and
{ meta: {keywords: "a"} ...}
I have this classes
public class Data {
#PropertyName("meta")
MetaData meta;
...
}
public class MetaData {
List<String> keywords;
...
}
and a custom deserializer
public static class CustomDeserilizer implements JsonDeserializer<MetaData>{
#Override
public MetaData deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
List<String> keywords = null;
Gson gson = new Gson();
MetaData metaData = gson.fromJson(json, AppMetaData.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("keywords")) {
JsonElement elem = jsonObject.get("keywords");
if (elem != null && !elem.isJsonNull()) {
if (jsonObject.get("keywords").isJsonArray()) {
keywords = gson.fromJson(jsonObject.get("keywords"), new TypeToken<List<String>>() {
}.getType());
} else {
String keywordString = gson.fromJson(jsonObject.get("keywords"), String.class);
keywords = new ArrayList<String>();
list.addAll(Arrays.asList(keywordString.split(",")));
}
}
}
metaData.setKeywords(keywords);
}
Then I try to apply the deserilizer:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Data.class,new CustomDeserilizer())
.create();
But I get a parsing error , because is trying to deserialize Data instead of MetaData, how can I apply this deserializer to make it work right?
I solved it creating a deserializer for my class Data.
public static class DataDeserilizer implements JsonDeserializer {
#Override
public Data deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
Gson gson = new Gson();
Data data = gson.fromJson(json, Data.class);
JsonObject jsonObject = json.getAsJsonObject();
if (jsonObject.has("meta")) {
JsonElement elem = jsonObject.get("meta");
if (elem != null && !elem.isJsonNull()) {
Gson gsonDeserializer = new GsonBuilder()
.registerTypeAdapter(MetaData.class, new CustomDeserilizer())
.create();
gsonDeserializer.fromJson(jsonObject.get("meta"), Data.class);
}
}
return data;
}
}
And
Gson gson = new GsonBuilder()
.registerTypeAdapter(Data.class,new DataDeserilizer())
.create();
Pretty obvious, but is there a more elegant solution?
Firstly, rename your class to meta instead of metadata and make keywords String instead of List.Then use the following to map your JSonString into your object.
Gson gson = new GsonBuilder().create();
Meta meta = gson.from(yourJsonString,Meta.class);
In order to get keywords only, you need this.
JsonObject jsonObject = new JsonObject(yourJSonString);
String data = jsonObject.getJsonObject("meta").getString("keywords");
keywords is a JsonObject not an JsonArray so you can't directly map it
onto List. You can split the string to get keywords in an array.
String keywords[] = data.split(",");
Here's a concise solution that leverages Java inheritance to represent the nested structure; and therefore does not need to provide any actual instance member fields (mappings, etc) for capturing the nested String data that GSON maps.
Step 1: For readability, create an empty object to represent the nested mapping
public class StateRegionCitiesMap extends HashMap<String, List<String>> {
}
Step 2: Add the one line of actual code to do the mapping; no other serialize/deserialize logic to manage
protected void loadContent(JsonObject stateRegionsJsonObject) {
HashMap<String, StateRegionCitiesMap> stateRegionCitiesMap =
mGson.fromJson(
stateRegionsJsonObject,
new TypeToken<HashMap<String, StateRegionCitiesMap>>() {
}.getType()
);
}
Alternatively, you can skip the wrapper class altogether and just directly put <String, List<String>> in the GSON call. However, I find an explicit object helps to inform/remind whoever is reading the code, what the purpose is.
Example JSON:
The class StateRegionCitiesMap represents a multi-tier map structure for say:
[US State] -> [State-Region Key] -> [Sub-Region Key] -> CitiesArray[]
"CA": {
"Central CA": {
"Central Valley": [
"FRESNO",
"VISALIA"
],
"Sacramento Area": [
"SACRAMENTO",
"EL DORADO HILLS"
]
},
This suppose to achieve what you want easily. You should define an inner static class. You can keep nesting classes to define keywords as class Keywords, etc. Just remember to have a field in the containing class, i.e.
in your inner class have private Keywords keywords;
In your Main class:
Gson gson = new Gson();
Data data = gson.fromJson(SOME_JSON_STRING, Data.class);
In a class called Data:
public class Data {
private Meta meta;
static class Meta{
private String[] keywords;
}
}
I'm reading a Document from MongoDB which contains its unique identifier:
{
"_id" : ObjectId("5526888bd3d56a86cea8ea12"),
"name" : "user1"
}
I'd like to map that with a java class
public class Mapper {
Object _id;
String name;
}
As a result of my fromJson execution:
Mapper m = gson.fromJson(string, Mapper.class);
...the value stored in the _id field is {$oid=5526888bd3d56a86cea8ea12}. I'd like to store the id String in it. (e.g. "5526888bd3d56a86cea8ea12")
Can Gson do it for me automatically ?
Thanks
You can register a custom adapter to tell the parser that you only want to grab the value between parenthesis and quotes in the string value (note that id is now a String in your Mapper class). The regex can be changed to match the ids requirements that are generated.
class MapperAdapter implements JsonDeserializer<Mapper> {
private static final Pattern p = Pattern.compile("\\(\"([a-zA-Z\\d]+)\"\\)");
#Override
public Mapper deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jObj = json.getAsJsonObject();
String id = jObj.get("_id").getAsString();
String name = jObj.get("name").getAsString();
Matcher m = p.matcher(id);
if(!m.find()) {
throw new IllegalArgumentException("The id should be within parenthesis and quotes.");
}
return new Mapper(m.group(1), name);
}
}
and you register it in your parser:
Gson gson = new GsonBuilder().registerTypeAdapter(Mapper.class, new MapperAdapter()).create();
Mapper m = gson.fromJson(jsonString, Mapper.class);
This yield the output:
Mapper{id=5526888bd3d56a86cea8ea12, name=user1}
I have a problem with parsing a JSON response using Gson.
JSON string:
response: [
2, {
owner_id: 23972237,
album_id: 25487692,
title: 'album not new'
}, {
owner_id: 23972237,
album_id: 25486631,
title: 'фыв'
}
]
I have these 2 classes:
public class VkAudioAlbumsResponse {
public ArrayList<VkAudioAlbum> response;
public VkError error;
}
public class VkAudioAlbum {
public int owner_id;
public int album_id;
public String title;
}
But I have an Exception when parse this using Gson.
I know this is because response array first element is not an object, but integer.
So the question is, can I solve it somehow?
You have to write a custom deserializer. I'd do something like this:
First you need to include a new class, further than the 2 you already have:
public class Response {
public VkAudioAlbumsResponse response;
}
And then you need a custom deserializer, something similar to this:
private class VkAudioAlbumsResponseDeserializer
implements JsonDeserializer<VkAudioAlbumsResponse> {
#Override
public VkAudioAlbumsResponse deserialize(JsonElement json, Type type,
JsonDeserializationContext context) throws JsonParseException {
JsonArray jArray = (JsonArray) json;
int firstInteger = jArray.get(0); //ignore the 1st int
VkAudioAlbumsResponse vkAudioAlbumsResponse = new VkAudioAlbumsResponse();
for (int i=1; i<jArray.size(); i++) {
JsonObject jObject = (JsonObject) jArray.get(i);
//assuming you have the suitable constructor...
VkAudioAlbum album = new VkAudioAlbum(jObject.get("owner_id").getAsInt(),
jObject.get("album_id").getAsInt(),
jObject.get("title").getAsString());
vkAudioAlbumsResponse.getResponse().add(album);
}
return vkAudioAlbumsResponse;
}
}
Then you have to deserialize your JSON like:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(VkAudioAlbumsResponse.class, new VkAudioAlbumsResponseDeserializer());
Gson gson = gsonBuilder.create();
Response response = gson.fromJson(jsonString, Response.class);
With this approach, when Gson tries to deserialize the JSON into Response class, it finds that there is an attribute response in that class that matches the name in the JSON response, so it continues parsing.
Then it realises that this attribute is of type VkAudioAlbumsResponse, so it uses the custom deserializer you have created to parse it, which process the remaining portion of the JSON response and returns an object of VkAudioAlbumsResponse.
Note: The code into the deserializer is quite straightforward, so I guess you'll have no problem to understand it... For further info see Gson API Javadoc
I have a simple Json structure like:
{"MessageType":"TimeData","TimeData":{"hh":12,"mm":13,"ms":15,"ss":14}}
and I devised the following classes to deserialize it:
public class JsonMessage
{
public enum MessageTypes{
WhoAreYou,
TimeData
}
JsonMessage(){
}
public MessageTypes MessageType;
}
class TimeData extends JsonMessage{
int hh;
int mm;
int ss;
int ms;
TimeData() {
}
}
I need to split deserialization into tow phases:
1- deserialize to read the MessageType.
2- proceed with the rest of deserialization based on the MessageType
The code is straightforward:
public void dispatch(Object message, IoSession session)
{
Gson gson = new Gson();
JsonMessage result = gson.fromJson(message.toString(), JsonMessage.class);
System.out.println(result.MessageType.toString());
switch (result.MessageType)
{
case WhoAreYou:{
//.....
break;
}
case TimeUpdate:
TimeData res = new Gson().fromJson(message.toString(), TimeData.class);
System.out.println(res.hh);
break;
default:break;
}
}
My Program can enter the correct switch-case(which is TimeUpdate) but it doesn't parse it correctly (The println prints 0 instead of 12)
where do you think I have done something wrong?
thank you
The issue is that your JSON represents an Object that contains another object you're interested in while your Java is just a single object.
You can actually just write deserializers for each type and use them once you determine the MessageType:
public static void main(String[] args)
{
Gson gson = new GsonBuilder().registerTypeAdapter(TimeData.class, new TimeDataDeserializer()).create();
String json = "{\"MessageType\":\"TimeData\",\"TimeData\":{\"hh\":12,\"mm\":13,\"ms\":15,\"ss\":14}}";
JsonMessage message = gson.fromJson(json, JsonMessage.class);
switch(message.MessageType)
{
case TimeData:
TimeData td = new GsonBuilder()
.registerTypeAdapter(TimeData.class, new TimeDataDeserializer())
.create()
.fromJson(json, TimeData.class);
td.MessageType = message.MessageType
System.out.println(td.hh);
break;
default:
break;
}
}
class TimeDataDeserializer implements JsonDeserializer<TimeData>
{
#Override
public TimeData deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)
throws JsonParseException
{
JsonObject jo = je.getAsJsonObject().getAsJsonObject("TimeData");
Gson g = new Gson();
return g.fromJson(jo, TimeData.class);
}
}
I managed to solve this similar problem by implementing a custom JsonDeserializer in the following way.
First you attach to your enum the subclasses based on the type and a method to retrieve the correct Class<?> according to the enum name:
enum MessageType {
WHO_ARE_YOU(WhoAreYou.class),
TIME_UPDATE(TimeUpdate.class);
public final Class<?> clazz;
MessageType(Class<?> clazz) { this.clazz = clazz; }
public static MessageType forName(String name) {
for (MessageType t : values())
if (name.equals(t.name()))
return t;
return NULL;
}
}
Then in the deserialize method I did the following:
public JsonMessage deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
JsonObject object = json.getAsJsonObject();
String kind = object.get("messageType").getAsString();
Class<?> clazz = MessageType.forName(kind).clazz;
JsonMessage result = null;
try {
result = (JsonMessage)clazz.newInstance();
Field[] fs = clazz.getFields();
for (Field f : fs) {
Object value = context.deserialize(object.get(f.getName()), f.getType());
if (value != null)
f.set(result, value);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
Everything is managed by reflection so that a correct object is created and then all fields are deserialized accordingly.
I had a complex hierarchy of objects so I preferred to go this way to let the gson deserializer manage everything. Of course you will need to register the serializer with the gson parser instance.
A NOTE: Your naming of things is quite incorrect according to Java standards. enum constants should be ALL_CAPITALIZED, enum class names should be singular (eg. MessageType). Instance variables should be camelcased (eg. messageType not MessageType)