How to make GSON fail on unknown properties - java

I need GSON mapper to throw an exception if json contains unknown fields. For example if we have POJO like
public class MyClass {
String name;
}
and json like
{
"name": "John",
"age": 30
}
I want to get some sort of message that json contains unknown field (age) that can not be deserialized.
I know there is out-of-box solution in Jackson mapper, but in our project we have been using Gson as a mapper for several years and using Jackson ends up in conflicts and bugs in different parts of project, so it is easier for me to write my own solution than using Jackson.
In other words, I want to know if there is some equivalent to Jackson's DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES in Gson. Or maybe if it can be done using Gson's DeserializationStrategy other than using reflections

I believe you cannot do it automatically with Gson.
I had to do this in a project at work. I did the following:
Gson GSON = new GsonBuilder().create();
(static final) Map<String, Field> FIELDS = Arrays.stream(MyClass.class.getDeclaredFields())
.collect(Collectors.toMap(Field::getName, Function.identity()));
JsonObject object = (JsonObject) GSON.fromJson(json, JsonObject.class);
List<String> objectProperties = object.entrySet().stream().map(Entry::getKey).collect(Collectors.toList());
List<String> classFieldNames = new ArrayList<>(FIELDS.keySet());
if (!classFieldNames.containsAll(objectProperties)) {
List<String> invalidProperties = new ArrayList<>(objectProperties);
invalidProperties.removeAll(classFieldNames);
throw new RuntimeException("Invalid fields: " + invalidProperties);
}

Related

Why parsing Invalid json returns Empty Object [duplicate]

I need GSON mapper to throw an exception if json contains unknown fields. For example if we have POJO like
public class MyClass {
String name;
}
and json like
{
"name": "John",
"age": 30
}
I want to get some sort of message that json contains unknown field (age) that can not be deserialized.
I know there is out-of-box solution in Jackson mapper, but in our project we have been using Gson as a mapper for several years and using Jackson ends up in conflicts and bugs in different parts of project, so it is easier for me to write my own solution than using Jackson.
In other words, I want to know if there is some equivalent to Jackson's DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES in Gson. Or maybe if it can be done using Gson's DeserializationStrategy other than using reflections
I believe you cannot do it automatically with Gson.
I had to do this in a project at work. I did the following:
Gson GSON = new GsonBuilder().create();
(static final) Map<String, Field> FIELDS = Arrays.stream(MyClass.class.getDeclaredFields())
.collect(Collectors.toMap(Field::getName, Function.identity()));
JsonObject object = (JsonObject) GSON.fromJson(json, JsonObject.class);
List<String> objectProperties = object.entrySet().stream().map(Entry::getKey).collect(Collectors.toList());
List<String> classFieldNames = new ArrayList<>(FIELDS.keySet());
if (!classFieldNames.containsAll(objectProperties)) {
List<String> invalidProperties = new ArrayList<>(objectProperties);
invalidProperties.removeAll(classFieldNames);
throw new RuntimeException("Invalid fields: " + invalidProperties);
}

Which Java/JSON library supports converting a JSON file that contains comments to a Java class?

My JSON file's content is :
{
"MJ" : "Michael Jordan",
"KB" : "Kobe Bryant",
"KG" : "Kevin Garnet"
}
Now it is easy to convert this file (or string) to a Java class (e.g: java.util.HashMap) if I use Gson or Jackson or JsonSimple or another Java/JSON third-party library. E.g., I can use Gson like this:
//this returns the string above
String jsonString = TestReader.getStrFromJSonFile();
Gson gson = new Gson();
Type type = new TypeToken<HashMap<String, String>>() {}.getType();
HashMap<String, String> map = gson.fromJson(jsonString, type);
But,if my JSON file contains comments, most of the third-party libraries will not work.
e.g.:
{
//Bulls
"MJ" : "Michael Jordan",
/*Lakers*/
"KB" : "Kobe Bryant",
//Boston Celtics
"KG" : "Kevin Garnet"
}
Now I'm even confused after googling and stackoverflowing a lot. I have two questions:
Can a JSON file really contain any comments? I think it can because I see a lot of this in JSON files everyday. But >>> (links)
If it can, how can I solve my problem?
Jackson supports comments if you enable that feature on the parser you use:
https://fasterxml.github.io/jackson-core/javadoc/2.10/com/fasterxml/jackson/core/JsonParser.Feature.html

jackson deserialization of json with a numbered array

I have a JSON string like this:
{
"1": {
"entity_id":"1",
"status":"canceled"
},
"2": {
"entity_id":"2",
"status":"pending"
}
}
I am struggling to find the correct settings to use when deserializing this. I stumble on what looks like mapping the "1" and "2" id's in the above.
My object mapper looks like this:
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationConfig.Feature.READ_ENUMS_USING_TO_STRING, true);
mapper.configure(DeserializationConfig.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
I've tried all sorts of classes to map it to, but none of them have worked when using the following line:
XXX jsonObject = mapper.readValue(json, XXX.class);
Any suggestions on what the XXX class should look like?
The solution was to map the json into a map as follows:
Map<String, Object> map = mapper.readValue(message, new TypeReference<Map<String, Object>>() {});
In the actual code I replaced Object with a java class that maps the fields such as entity_id and status

Creating JSON objects directly from model classes in Java

I have some model classes like Customer, Product, etc. in my project which have several fields and their setter-getter methods, I need to exchange objects of these classes as a JSONObject via Sockets to and from client and server.
Is there any way I can create JSONObject directly from the object of model class such that fields of the object become keys and values of that model class object become values for this JSONObject.
Example:
Customer c = new Customer();
c.setName("Foo Bar");
c.setCity("Atlantis");
.....
/* More such setters and corresponding getters when I need the values */
.....
And I create JSON Object as:
JSONObject jsonc = new JSONObject(c); //I'll use this only once I'm done setting all values.
Which gets me something like:
{"name":"Foo Bar","city":"Atlantis"...}
Please note that, in some of my model classes, certain properties are itself an object of other model class. Such as:
Product p = new Product();
p.setName("FooBar Cookies");
p.setProductType("Food");
c.setBoughtProduct(p);
In a case like above, as I'd expect, the yielded JSON object would be:
{"name":"Foo Bar","city":"Atlantis","bought":{"productname":"FooBar Cookies","producttype":"food"}}
I know I could create something like toJSONString() in each model class and have the JSON-friendly string created and manipulate it then, but in my previous experiences of creating RESTful service in Java (which is totally out of context for this question), I could return JSON string from the service method by using #Produces(MediaType.APPLICATION_JSON) and have the method returning object of model class. So it produced JSON string, which I could consume at the client end.
I was wondering if it's possible to get similar behavior in current scenario.
Google GSON does this; I've used it on several projects and it's simple and works well. It can do the translation for simple objects with no intervention, but there's a mechanism for customizing the translation (in both directions,) as well.
Gson g = ...;
String jsonString = g.toJson(new Customer());
You can use Gson for that:
Maven dependency:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.0</version>
</dependency>
Java code:
Customer customer = new Customer();
Product product = new Product();
// Set your values ...
Gson gson = new Gson();
String json = gson.toJson(customer);
Customer deserialized = gson.fromJson(json, Customer.class);
User = new User();
Gson gson = new Gson();
String jsonString = gson.toJson(user);
try {
JSONObject request = new JSONObject(jsonString);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Use gson to achieve this. You can use following code to get the json then
Gson gson = new Gson();
String json = gson.toJson(yourObject);
I have used XStream Parser to
Product p = new Product();
p.setName("FooBar Cookies");
p.setProductType("Food");
c.setBoughtProduct(p);
XStream xstream = new XStream(new JettisonMappedXmlDriver());
xstream.setMode(XStream.NO_REFERENCES);
xstream.alias("p", Product.class);
String jSONMsg=xstream.toXML(product);
System.out.println(xstream.toXML(product));
Which will give you JSON string array.

In XStream is there a better way to marshall/unmarshall List<Object>'s in JSON and Java

I'm using XStream and JETTISON's Stax JSON serializer to send/receive messages to/from JSON javascripts clients and Java web applications.
I want to be able to create a list of objects to send to the server and be properly marshalled into Java but the format that XStream and JSON expect it in is very non-intuitive and requires our javascript libraries to jump through hoops.
[EDIT Update issues using GSON library]
I attempted to use the GSON library but it cannot deserialize concrete objects when I only have it expect generic super classes (XStream and Jettison handles this because type information is baked into the serialization).
GSON FAQ states Collection Limitation:
Collections Limitations
Can serialize collection of arbitrary objects but can not deserialize from it
Because there is no way for the user to indicate the type of the resulting object
While deserializing, Collection must be of a specific generic type
Maybe I'm using bad java practices but how would I go about building a JSON to Java messaging framework that sent/received various concrete Message objects in JSON format?
For example this fails:
public static void main(String[] args) {
Gson gson = new Gson();
MockMessage mock1 = new MockMessage();
MockMessage mock2 = new MockMessage();
MockMessageOther mock3 = new MockMessageOther();
List<MockMessage> messages = new ArrayList<MockMessage>();
messages.add(mock1);
messages.add(mock2);
messages.add(mock3);
String jsonString = gson.toJson(messages);
//JSON list format is non-intuitive single element array with class name fields
System.out.println(jsonString);
List gsonJSONUnmarshalledMessages = (List)gson.fromJson(jsonString, List.class);
//This will print 3 messages unmarshalled
System.out.println("XStream format JSON Number of messages unmarshalled: " + gsonJSONUnmarshalledMessages.size());
}
[{"val":1},{"val":1},{"otherVal":1,"val":1}]
Exception in thread "main" com.google.gson.JsonParseException: The JsonDeserializer com.google.gson.DefaultTypeAdapters$CollectionTypeAdapter#638bd7f1 failed to deserialized json object [{"val":1},{"val":1},{"otherVal":1,"val":1}] given the type interface java.util.List
Here's an example, I want to send a list of 3 Message objects, 2 are of the same type and the 3rd is a different type.
import java.util.ArrayList;
import java.util.List;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
class MockMessage {
int val = 1;
}
class MockMessageOther {
int otherVal = 1;
}
public class TestJSONXStream {
public static void main(String[] args) {
JettisonMappedXmlDriver xmlDriver = new JettisonMappedXmlDriver();
XStream xstream = new XStream(xmlDriver);
MockMessage mock1 = new MockMessage();
MockMessage mock2 = new MockMessage();
MockMessageOther mock3 = new MockMessageOther();
List messages = new ArrayList();
messages.add(mock1);
messages.add(mock2);
messages.add(mock3);
String jsonString = xstream.toXML(messages);
//JSON list format is non-intuitive single element array with class name fields
System.out.println(jsonString);
List xstreamJSONUnmarshalledMessages = (List)xstream.fromXML(jsonString);
//This will print 3 messages unmarshalled
System.out.println("XStream format JSON Number of messages unmarshalled: " + xstreamJSONUnmarshalledMessages.size());
//Attempt to deserialize a reasonable looking JSON string
String jsonTest =
"{"+
"\"list\" : ["+
"{"+
"\"MockMessage\" : {"+
"\"val\" : 1"+
"}"+
"}, {"+
"\"MockMessage\" : {"+
"\"val\" : 1"+
"}"+
"}, {"+
"\"MockMessageOther\" : {"+
"\"otherVal\" : 1"+
"}"+
"} ]"+
"};";
List unmarshalledMessages = (List)xstream.fromXML(jsonTest);
//We expect 3 messages but XStream only deserializes one
System.out.println("Normal format JSON Number of messages unmarshalled: " + unmarshalledMessages.size());
}
}
Intuitively I expect the XStream JSON to be serialized (and able to deserialize correctly) from the following format:
{
"list" : [
{
"MockMessage" : {
"val" : 1
}
}, {
"MockMessage" : {
"val" : 1
}
}, {
"MockMessageOther" : {
"otherVal" : 1
}
} ]
}
Instead XStream creates a single element list with fields that are named the classnames and nested arrays of Objects of the same type.
{
"list" : [ {
"MockMessage" : [ {
"val" : 1
}, {
"val" : 1
} ],
"MockMessageOther" : {
"otherVal" : 1
}
} ]
}
The trouble may be caused by it using the XStream XML CollectionConverter?
Does anyone have a suggestion for a good JSON Java object serialization that allows you to read/write arbitrary Java objects. I looked at the Jackson Java JSON Processor but when you were reading in objects from a stream you had to specify what type of object it was unlike XStream where it will read in any object (because the serialized XStream JSON contains class name information).
I agree with other poster in that XStream is not a good fit -- it's an OXM (Object/Xml Mapper), and JSON is handled as a secondary output format using XML processing path. This is why a "convention" (of how to convert hierarchich xml model into object-graph model of json and vice versa) is needed; and your choice boils down to using whatever is least intrusive of sub-optimal choices.
That works ok if XML is your primary data format, and you just need some rudimentary JSON(-like) support.
To get good JSON-support, I would consider using a JSON processing library that does real OJM mapping (I assume Svenson does too, but additionally), such as:
Jackson
Google-gson
Also: even if you do need to support both XML and JSON, you are IMO better off using separate libraries for these tasks -- objects (beans) to use on server-side need not be different, just serialization libs that convert to/from xml and json.
I realize this is off-topic, but I'd like to present a solution in svenson JSON.
Do you really need public fields in your domain classes? Apart from having to use properties, svenson can handle cases like this with a more simple JSON output with a discriminator property
class Message
{
// .. your properties with getters and setters ..
// special property "type" acts a signal for conversion
}
class MessageOther
{
...
}
List list = new ArrayList();
list.add(new Message());
list.add(new MessageOther());
list.add(new Message());
String jsonDataSet = JSON.defaultJSON().forValue(list);
would output JSON like
[
{"type":"message", ... },
{"type":"message_other", ... },
{"type":"message", ... }
]
which could be parsed again with code like this
// configure reusable parse instance
JSONParser parser = new JSONParser();
// type mapper to map to your types
PropertyValueBasedTypeMapper mapper = new PropertyValueBasedTypeMapper();
mapper.setParsePathInfo("[]");
mapper.addFieldValueMapping("message", Message.class);
mapper.addFieldValueMapping("message_other", MessageOther.class);
parser.setTypeMapper(mapper);
List list = parser.parse(List.class, jsonDataset);
A svenson type mapper based on the full class name would look something like this
public class ClassNameBasedTypeMapper extends PropertyValueBasedTypeMapper
{
protected Class getTypeHintFromTypeProperty(String value) throws IllegalStateException
{
try
{
return Class.forName(value);
}
catch (ClassNotFoundException e)
{
throw new IllegalStateException(value + " is no valid class", e);
}
}
}
which is not an ideal implementation as it inherits the configuration of PropertyValueBasedTypeMapper without really needing. (should include a cleaner version in svenson)
The setup is very much like above
JSONParser parser = new JSONParser();
ClassNameBasedTypeMapper mapper = new ClassNameBasedTypeMapper();
mapper.setParsePathInfo("[]");
parser.setTypeMapper(mapper);
List foos = parser
.parse( List.class, "[{\"type\":\"package.Foo\"},{\"type\":\"package.Bar\"}]");

Categories