Exclusion Strategy in gson - java

My JSON has the following structure:
{"name": [9000, {Inst1}, ..., {Instn}]}
Where 9000 is an arbitrary integer and Insti are serialized instances of some class.
I'm using something like this for getting all the Inst into the list:
Type listType = new TypeToken<ArrayList<Song>>(){}.getType();
and trying go exclude that first int by writing something like this:
public class ExcludeTotalFound implements ExclusionStrategy {
private final Class<?> typeToSkip;
public ExcludeTotalFound(Class<?> typeToSkip) {
this.typeToSkip = typeToSkip;
}
public boolean shouldSkipClass(Class<?> clas_s) {
return clas_s == typeToSkip;
}
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return typeToSkip.equals(fieldAttributes.getDeclaredClass());
}
}
And, finally, I'm doing
gson = new GsonBuilder().addDeserializationExclusionStrategy(new ExcludeTotalFound(int.class)).serializeNulls().create();
and, then:
collection = gson.fromJson(rBody, listType);
where rBody is all that raw array, i.e. {"name": [9000, {Inst1}, ..., {Instn}]
But all what I get is
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was NUMBER`
What's the problem?
ADD:
As long, as I know that the length of my JSON will never exceed ~500, and that the structure remains always the same, is it good enough to use the following workaround?
Iterator<JsonElement> it = rBody.iterator();
it.next();
while (it.hasNext()) {
collection.add(gson.fromJson(it.next(), Song.class));
}

This looks pretty similar to this one I answered over here -->
Gson custom deserialization. Does that help? It's not by exclusion, but rather by custom deserialization.

Related

Create an universal java mapper for json string

I'm working on a Spring-boot project where I receive different format of Json String. My goal is to convert these Json string into an Unified Java class.
I can receive many variations of this Json:
{ "id" : "someId", "type" : "temperature", "value" : 21.0 }
For example, one variation might look like :
{ "id" : "someId", "data" : { "type": "temp", "val" : 21.0 }, "location": "here" }
So these 2 Json must be mapped into the same Java class.
I already have 2 solutions in mind :
First solution
1) Create a Specific Java Class for each Json that I may receive
2) Create a function that takes this specific object and return the Unified Java Class
Second solution
1) Create a JsonNode with the Json String
2) For each key try to match it with a field of the Unified Java Class.
But we have to take into consideration every key possible of a node like "value" or "val".
What is the best approach to solve this problem ?
I'm looking for a solution that could be easy to maintain.
Edit : I'm already using Jackson, but my problem is to map this Json object into an universal Java Class independently of the Json
Edit 2 : The Unified Java Class is a class model that already exist and it's used to store information in our database. So to push information inside our database, I have to convert each json I receive into this unified format
I can see following solutions. E.g. you use Jackson for parse JSON you could declare you custom ObjectMapper:
ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
This mapper contains additional options to ignore unknow properties.
Do you Map<String, Object> as destination class. This is magic key and it works always. Contra: you do not have json validation and have to add many constant keys to read this.
Example:
public static <T> Map<String, T> readMap(String json) throws NGPException {
if (json == null) {
return null;
}
ObjectReader reader = JSON_MAPPER.readerFor(Map.class);
MappingIterator<Map<String, T>> it = reader.readValues(json);
if (it.hasNextValue()) {
Map<String, T> res = it.next();
return res.isEmpty() ? Collections.emptyMap() : res;
}
return Collections.emptyMap();
}
Client:
Map<String, Object> map = readMap("json string");
String id = (String)map.getOrDefault("id", null);
Second way is to build one general class that contain all posiible variables. Additionnaly you have to set option to Jackson ignore unknown fields. In this case, existed fields will be used by Jackson.
Example:
public static <T> T read(String json, Class<T> clazz) throws NGPException {
return mapper.readerFor(clazz).readValue(json);
}
class Response {
private String id;
private String type;
private Double value;
private String location;
private Data data;
public class Data {
private String type;
private String temp;
private Double value;
}
}
Client:
Response response = read("json string", Response.class);
I usually use GSon from Google. It is really usefull. Check gson.fromJson(yourJsonString) in your case.
You can easy use
Gson gson = new Gson();
Data data = gson.fromJson(jsonString, Data.class);

Find out if class field annotated by Gson SerializedName actually existes in Json

I'm using google Gson to parse json and create an appropriate object:
public class Settings {
#SerializedName("version")
public String version = "1";
#SerializedName("ad_native_enabled")
public boolean nativeAdEnabled = false;
}
The problem is that I need to know if the value of nativeAdEnabled is actually parsed from json or it's the default value specified by me, i.e. does the ad_native_enabled key exist in json, or not? I've tried to use Boolean instead of boolean and just do null check, but Gson deserialisation failed. Here is the snippet of my json:
{
"status": "success",
"ad_native_enabled": false,
}
DISCLAIMER
In my situation it's not relevant and elegant to parse the json by hand and do the detection on that level(and I guess in this case I have to priorly owe the list of the keys the existence of which I want to check). It's highly desirable to somehow infer the needed information on the object level.
I described a problem on the example of a boolean, but the question may be generalised and may refer to all primitive types. So it'd be great to have a generic solution for this problem.
I know you said you already tried this, but using a Boolean field should work. I have reduced your example down a bit, and it works as expected.
I defined the Settings class like this:
public static class Settings {
#SerializedName("ad_native_enabled")
public Boolean nativeAdEnabled;
}
If you then parse JSON that contains the field:
String json = "{\"ad_native_enabled\": false}";
Settings settings = gson.fromJson(json, Settings.class);
System.out.println(settings.nativeAdEnabled); // prints false
Whereas if you parse JSON that does not contain the field:
String emptyJson = "{}";
Settings emptySettings = gson.fromJson(emptyJson, Settings.class);
System.out.println(emptySettings.nativeAdEnabled); // prints null
Did you perhaps leave the default value of the field as false? If so, the second example will print false as well. Also it seems GSON does not particularly like trailing commas after the last property in JSON objects - maybe that was why you were getting errors?
After seeing your comment, I thought a bit more about whether it is possible to somehow support default values, while still being able to tell whether the field was present in the JSON or not. The best solution I could come up with was to introduce a new wrapper type, with a custom deserializer.
I started by defining this wrapper type, which just contains the actual value of the field, and an indicator of whether this value is the default value or not:
public static class ValueWrapper<T> {
public final T value;
public final boolean isDefaultValue;
public ValueWrapper(T value, boolean isDefaultValue) {
this.value = value;
this.isDefaultValue = isDefaultValue;
}
}
The Settings class then looks like this:
public static class Settings {
#SerializedName("ad_native_enabled")
public ValueWrapper<Boolean> nativeAdEnabled = new ValueWrapper<>(false, true);
}
Here I have defined the field as having value false by default, which is why isDefaultValue is set to true.
I then defined a custom deserializer for this type. The basic idea is to take the type of ValueWrapper you are trying to deserialize, extract its generic parameter, deserialize the actual value in the JSON as the generic parameter type, and then return a new ValueWrapper where isDefaultValue is set to false. This deserializer looks like this:
public static class ValueWrapperDeserializer implements JsonDeserializer<ValueWrapper<?>> {
#Override
public ValueWrapper<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
ParameterizedType parameterizedType = (ParameterizedType) typeOfT;
Type valueType = parameterizedType.getActualTypeArguments()[0];
Object value = context.deserialize(json, valueType);
return new ValueWrapper<>(value, false);
}
}
Now all we need to do is register the custom deserializer:
Gson gson = new GsonBuilder()
.registerTypeAdapter(ValueWrapper.class, new ValueWrapperDeserializer())
.create();
And we can then run through my two examples from above:
String json = "{\"ad_native_enabled\": false}";
Settings settings = gson.fromJson(json, Settings.class);
System.out.println(settings.nativeAdEnabled.value); // prints false
System.out.println(settings.nativeAdEnabled.isDefaultValue); // prints false
String emptyJson = "{}";
Settings emptySettings = gson.fromJson(emptyJson, Settings.class);
System.out.println(emptySettings.nativeAdEnabled.value); // prints false
System.out.println(emptySettings.nativeAdEnabled.isDefaultValue); //prints true
So this allows us to have the default value, but still be able to tell whether the field was set or not using isDefaultValue. This obviously has quite an impact on the API to the user of the Settings object, but perhaps this is neater than handling nulls and storing the default values elsewhere.

Polymorphic Jackson deserialization: getting either a string or an array of strings

My question is pretty much identical to this one, except that I'm using Java/Jackson instead of C#:
In C# how can I deserialize this json when one field might be a string or an array of strings?
My input JSON can be this:
{ "foo": "a string" }
or this:
{ "foo": ["array", "of", "strings" ] }
My class looks like this:
class MyClass {
public List<String> foo;
}
If the input contains a single string, I want it to become the first entry in the list.
How can I deserialize foo using Jackson? I could write a custom deserializer, which I've done before, but I thought there might be an easier way.
There is a feature called ACCEPT_SINGLE_VALUE_AS_ARRAY which is turned off by default but you can turn it on:
objectMapper = new ObjectMapper()
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
You can also turn it on per case:
class SomeClass {
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<String> items;
// ...
}

Gson parse unquoted value

I using in my project GSON library, everything is fine, but now i'm stuck with a problem, where i need to use a custom deserializer on unquoted values.
I have the following value, and need to parse from json:
[ ["county","=", field_name], ["name", "ilike", "username"] ]
I need to parse unquoted values with a custom deserializer, to a wrapper class like:
public class StringField {
private String value;
public String getValue() {
return value;
}
}
And value will have "field_name" as string.
The problem is that the data is not valid JSON.
JSON does not permit such "unquoted value" strings such as field_name and neither does Gson. Either fix the input such that it is valid JSON (perhaps "$field_name$") - or use a tool (i.e. not Gson) that can cope with non-JSON text that resembles JSON.
This situation can't be corrected with Custom Deserialization because the data isn't even parsed correctly to Json tokens: Gson will throw an exception as the invalid/non-JSON is encountered.
At the very least this would require creating a customized JsonReader implementation that can read "barewords" as strings. However, this is problematic to do because JsonReader does not conform to any specialized interfaces (so it must be subclassed, oops!) and is final (so it can't be subclassed, oops!). As such, unless willing to edit the Gson library source: not possible.
With the below code, I parsed your JSON without problems, I left Gson decide how to parse it, except assuming it contained a List outermost. And the result was a List of Lists of Strings. I did not understand very well why you need StringField class.
package stackoverflow.questions;
import java.util.List;
import com.google.gson.*;
public class Q20557131 {
public static void main(String[] args){
String json = "[[\"county\",\"=\", field_name], [\"name\", \"ilike\", \"username\"]]";
Gson g = new Gson();
List outerList = g.fromJson(json, List.class);
List innerList = (List) outerList.get(0);
for(Object o: innerList)
System.out.println(o.getClass());
}
}
By default, Gson 2.2.4 is lenient, even if has the lenient property set to false, from documentation
Configure this parser to be be liberal in what it accepts. By default, this parser is strict and only accepts JSON as specified by RFC 4627. Setting the parser to lenient causes it to ignore the following syntax errors:
....
Strings that are unquoted or 'single quoted'.
...
even if documentation states that property is false by default, in the source code of the JsonReader#fromJson:
public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
boolean isEmpty = true;
boolean oldLenient = reader.isLenient();
reader.setLenient(true); <-- always true
try {
reader.peek();
isEmpty = false;
TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
TypeAdapter<T> typeAdapter = getAdapter(typeToken);
T object = typeAdapter.read(reader);
return object;
} catch (EOFException e) {
...
I've solved this problem years ago in another way (sorry for delayed). Wrote symbolic preprocessor class, which replace by regexp labels like field_name with actual values from model and then parsed json.

Deserialization of generic collections with Gson

I have some difficulties with json deserialization using GSon and I hope somebody can help me.
I want to deserialize the following json snippet:
{
"fieldA": "valueA",
"myCollection": {
"AnotherClass": [
{
"objectAfieldA": "valueB",
"objectAfieldB": "valueC"
},
{
"objectAfieldA": "valueD",
"objectAfieldB": "valueE"
}
]
}
}
the corresponding overall class has following fields:
...
String fieldA;
List<AnotherClass> = new ArrayList<AnotherClass>();
....
Now, my problem is that when I deserialize, using fromJson(jsonSample, resultContainer.class), without the List<T> element, everything is good, but I get a NullPointerException when I include the contained list. I've read about how to deal with collections of generic types and the use of TypeToken, but I can't apply this knowledge when my collection is part of another class…
I really would appreciate any help to solve this.
The solution for deserealizing the unnamed JSON array is quite simple:
List<resultContainer> lres = gson.fromJson(new FileReader("input.json"), new TypeToken<List<resultContainer>>(){}.getType());
When deserializing, you only need to use the TypeToken if the outer-most structure to be deserialized into is a generic collection. This is not the case for the example in the original question. So, use of a TypeToken is unnecessary.
The issue appears to be that the JSON structure does not match the Java structure attempting to be bound to.
The JSON structure defines
an object with two elements
element 1 is a string named "fieldA",
element 2 is an object named "myCollection", which has one element
the one element is an array named "AnotherClass", composed of objects with two elements
element 1 is a string named "objectAfieldA",
element 2 is a string named "objectAfieldB"
So, define a Java data structure to match that, and deserialization will work very simply, without any custom processing necessary. If such a matching Java structure is not provided, then custom deserialization is necessary.
Here is such a working example using the names and types from the original question.
import java.io.FileReader;
import com.google.gson.Gson;
public class Foo
{
public static void main(String[] args) throws Exception
{
Gson gson = new Gson();
resultContainer result = gson.fromJson(new FileReader("input.json"), resultContainer.class);
System.out.println(gson.toJson(result));
}
}
class resultContainer
{
String fieldA;
MyCollectionContainer myCollection;
}
class MyCollectionContainer
{
SomeOtherClass[] AnotherClass;
}
class SomeOtherClass
{
String objectAfieldA;
String objectAfieldB;
}

Categories

Resources