Java JSON serialization - best practice - java

I need to implement JSON serialization for some objects, and I've encountered a problem when it came to integration with generic collections.
All serializable classes implement this interface (JSONObject comes from this library):
interface JSONSerializable{
public JSONObject dump() throws JSONException //serializes object
public void load(JSONObject obj) throws JSONException //deserializes object
}
Code for my collection based on java.util.list looks more or less like this:
class AwesomeList<T extends JSONSerializable> implements JSONSerializable{
private LinkedList<T> items = new LinkedList<T>();
...
...
public JSONObject dump() throws JSONException {
JSONObject result = new JSONObject();
JSONArray a = new JSONArray();
for(T i : items){
a.put(i.dump());
}
result.put("items", a);
return result;
}
public void load(JSONObject obj) throws JSONException{
//here is my problem
}
}
My problem is: When I load AwesomeList from JSONObject, I need to create its elements but it's impossible since java forbids me to write
T newItem = new T();
newItem.load(obj);
How should I modify my approach to this task?

Are you tied to this library? Google Gson is very popular. I have myself not used it with Generics but their front page says Gson considers support for Generics very important.

As others have hinted, you should consider dumping org.json's library. It's pretty much obsolete these days, and trying to work around its problems is waste of time.
But to specific question; type variable T just does not have any information to help you, as it is little more than compile-time information.
Instead you need to pass actual class (as 'Class cls' argument), and you can then create an instance with 'cls.newInstance()'.

Well, when writing it out to file, you do know what class T is, so you can store that in dump. Then, when reading it back in, you can dynamically call it using reflection.
public JSONObject dump() throws JSONException {
JSONObject result = new JSONObject();
JSONArray a = new JSONArray();
for(T i : items){
a.put(i.dump());
// inside this i.dump(), store "class-name"
}
result.put("items", a);
return result;
}
public void load(JSONObject obj) throws JSONException {
JSONArray arrayItems = obj.getJSONArray("items");
for (int i = 0; i < arrayItems.length(); i++) {
JSONObject item = arrayItems.getJSONObject(i);
String className = item.getString("class-name");
try {
Class<?> clazzy = Class.forName(className);
T newItem = (T) clazzy.newInstance();
newItem.load(obj);
items.add(newItem);
} catch (InstantiationException e) {
// whatever
} catch (IllegalAccessException e) {
// whatever
} catch (ClassNotFoundException e) {
// whatever
}
}

Related

Asserting object is a valid top level json serializable

I would like to make sure o is a serializable top level JSON object, that is [] or {} else throw an exception. I have tried the following code using "" and null as input but they are not triggering an exception.
static void checkIsjsonSerializable(Object o, String message)
throws MissingRequiredValueException {
try{
Gson gson = new Gson();
gson.toJson(o);
} catch (Exception e) {
throw new MissingRequiredValueException(message);
}
}
What would need to change to get the check I want?
Update:
After comments it clear my understanding was wrong. My question has change to:
How can I assert only [] and {} are valid in the following function?
As others have mentioned, modern definitions of JSON do allow primitives (strings, numbers, booleans, null) as top-level elements. But if you really need to do this check with GSON, here's one option:
private static final Gson gson = new Gson();
static void checkIsjsonSerializable(Object o, String message)
throws MissingRequiredValueException {
JsonElement rootElement = gson.toJsonTree(o);
if (!rootElement.isJsonArray() && !rootElement.isJsonObject()) {
throw new MissingRequiredValueException(message);
}
}

Odd behaviour with hibernate JSON converter

I am having some trouble with converting a Set of POJO's to a JSON string so I can store it in a database column. The conversion to JSON works as expected but when the conversion from JSON to a Set< Qualification > happens it always returns a LinkedHashSet<LinkedHashMap> which is causing issues.
The weird thing is that inside my converter the JSON string is converted successfully to a Set<Qualification>. When I debug in my IDE and step through the execution I can see that after it calls a deepCopy method in the MutableMutabilityPlan abstract class. At this point the data is of type LinkedHashSet<LinkedHashMap> and not Set<Qualification> from the conversion.
Here is my converter.
public class SetToStringConverter implements AttributeConverter<Set<Qualification>, String> {
private final ObjectMapper mapper = new ObjectMapper();
private final String errorMessage = "converter.invalid";
#Override
public String convertToDatabaseColumn(final Set<Qualification> items) {
try {
return mapper.writeValueAsString(items);
} catch (JsonProcessingException e) {
throw new ConverterFailureException(errorMessage);
}
}
#Override
public Set<Qualification> convertToEntityAttribute(final String data) {
try {
if (data != null) {
final Set<Qualification> s = mapper.readValue(data, new TypeReference<Set<Qualification>>() {});
return s; // Debugging here I have the correct type
}
return new HashSet<>();
} catch (IOException e) {
throw new ConverterFailureException(errorMessage);
}
}
}
I have done some research and have tried various approaches but the result is always the same.
Has anyone run into this issue before or can see anything wrong with my converter. Let me know if anything isn't clear so I can provide more information.
Thanks very much for the help.

How to serialize/deserialize two different list types into single sequential file

I have three different lists of different contact types that I need to serialize into a single file and then retrieve those lists when needed (deserialize). I've thought about using a hashmap but I'm not familiar with and I'm sure how I would retrieve the lists intact. Any ideas are appreciated.
I'm not sure if I can just use an Object type in the hashmap. It's the one way I'm able to add all three lists to the hashmap.
Also I need to know the correct way to retrieve those lists from the hashmap if that's the best approach.
public class Controller()
{
// the list objects I need to serialize
List<FamilyContact> friendContacts = new ArrayList<FamilyContact>();
List<Contact> fdContacts = new ArrayList<>(friendContacts);
List<FamilyContact> familyContacts = new ArrayList<FamilyContact>();
List<Contact> fContacts = new ArrayList<>(familyContacts);
// methods to retrieve the lists and list items
}
//Serialization code
public class Serialization
{
public void serialize(HashMap<String, Object> lists, String fileName)
{
// serializes the hashmap passed from calling method
try (ObjectOutputStream output =
new ObjectOutputStream(new FileOutputStream(fileName)))
{
output.writeObject(lists);
output.close();
}
catch(IOException ex)
{
System.out.println(ex.getMessage());
ex.printStackTrace();
}
}
#SuppressWarnings("unchecked")
public HashMap<String, Object> deserialize(String fileName)
{
try (ObjectInputStream input =
new ObjectInputStream(new FileInputStream(fileName)))
{
HashMap<String, Object> lists = (HashMap)input.readObject();
System.out.println(lists.size());
input.close();
return lists;
}
catch (IOException | ClassNotFoundException ex)
{
if(ex.getClass().getName() == "java.io.FileNotFoundException")
{
showErrorDialog("File Not Found", "Contacts.ser not found");
}
else
{
System.out.println(ex.getClass().getName());
System.out.println(ex.getMessage());
}
}
return null;
}
When you deserialise an object it [usually] comes out as the same type as you serialised it. So serialisation is a red herring.
Using a Map in such situations is usually an unnecessary thing to do. Particularly as you have different types, which will lead to casting.
So you will probably want an [immutable] value type, written in the usual [overly verbose] way.

Ignore specific nodes/attributes while comparing two JSONs

I want to compare two JSON strings which is a huge hierarchy and want to know where they differ in values. But some values are generated at runtime and are dynamic. I want to ignore those particular nodes from my comparison.
I am currently using JSONAssert from org.SkyScreamer to do the comparison. It gives me nice console output but does not ignore any attributes.
for ex.
java.lang.AssertionError messageHeader.sentTime
expected:null
got:09082016 18:49:41.123
Now this comes dynamic and should be ignored. Something like
JSONAssert.assertEquals(expectedJSONString, actualJSONString,JSONCompareMode, *list of attributes to be ignored*)
It would be great if someone suggests a solution in JSONAssert. However other ways are also welcome.
You can use Customization for this. For example, if you need to ignore a top-level attribute named "timestamp" use:
JSONAssert.assertEquals(expectedResponseBody, responseBody,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization("timestamp", (o1, o2) -> true)));
It's also possible to use path expressions like "entry.id". In your Customization you can use whatever method you like to compare the two values. The example above always returns true, no matter what the expected value and the actual value are. You could do more complicated stuff there if you need to.
It is perfectly fine to ignore that values of multiple attributes, for example:
#Test
public void ignoringMultipleAttributesWorks() throws JSONException {
String expected = "{\"timestamp\":1234567, \"a\":5, \"b\":3 }";
String actual = "{\"timestamp\":987654, \"a\":1, \"b\":3 }";
JSONAssert.assertEquals(expected, actual,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization("timestamp", (o1, o2) -> true),
new Customization("a", (o1, o2) -> true)
));
}
There is one caveat when using Customizations: The attribute whose value is to be compared in a custom way has to be present in the actual JSON. If you want the comparison to succeed even if the attribute is not present at all you would have to override CustomComparator for example like this:
#Test
public void extendingCustomComparatorToAllowToCompletelyIgnoreCertainAttributes() throws JSONException {
// AttributeIgnoringComparator completely ignores some of the expected attributes
class AttributeIgnoringComparator extends CustomComparator{
private final Set<String> attributesToIgnore;
private AttributeIgnoringComparator(JSONCompareMode mode, Set<String> attributesToIgnore, Customization... customizations) {
super(mode, customizations);
this.attributesToIgnore = attributesToIgnore;
}
protected void checkJsonObjectKeysExpectedInActual(String prefix, JSONObject expected, JSONObject actual, JSONCompareResult result) throws JSONException {
Set<String> expectedKeys = getKeys(expected);
expectedKeys.removeAll(attributesToIgnore);
for (String key : expectedKeys) {
Object expectedValue = expected.get(key);
if (actual.has(key)) {
Object actualValue = actual.get(key);
compareValues(qualify(prefix, key), expectedValue, actualValue, result);
} else {
result.missing(prefix, key);
}
}
}
}
String expected = "{\"timestamp\":1234567, \"a\":5}";
String actual = "{\"a\":5}";
JSONAssert.assertEquals(expected, actual,
new AttributeIgnoringComparator(JSONCompareMode.LENIENT,
new HashSet<>(Arrays.asList("timestamp")))
);
}
(With this approach you still could use Customizations to compare other attributes' values in the way you want.)
you can use JsonUnit It has the functionality that you are looking for we can ignore fields, paths, and values that are null etc. Check it out for more info. As for the example, you can ignore a path like this
assertJsonEquals(
"{\"root\":{\"test\":1, \"ignored\": 2}}",
"{\"root\":{\"test\":1, \"ignored\": 1}}",
whenIgnoringPaths("root.ignored")
);
Sometimes you need to ignore certain values when comparing. It is possible to use ${json-unit.ignore} placeholder like this
assertJsonEquals("{\"test\":\"${json-unit.ignore}\"}",
"{\n\"test\": {\"object\" : {\"another\" : 1}}}");
First of all there is open issue for it.
In my tests I compare json from controller with actual object with help of JsonUtil class for serialization/deserialization:
public class JsonUtil {
public static <T> List<T> readValues(String json, Class<T> clazz) {
ObjectReader reader = getMapper().readerFor(clazz);
try {
return reader.<T>readValues(json).readAll();
} catch (IOException e) {
throw new IllegalArgumentException("Invalid read array from JSON:\n'" + json + "'", e);
}
}
public static <T> T readValue(String json, Class<T> clazz) {
try {
return getMapper().readValue(json, clazz);
} catch (IOException e) {
throw new IllegalArgumentException("Invalid read from JSON:\n'" + json + "'", e);
}
}
public static <T> String writeValue(T obj) {
try {
return getMapper().writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e);
}
}
To ignore specific object field I've add new method:
public static <T> String writeIgnoreProps(T obj, String... ignoreProps) {
try {
Map<String, Object> map = getMapper().convertValue(obj, new TypeReference<Map<String, Object>>() {});
for (String prop : ignoreProps) {
map.remove(prop);
}
return getMapper().writeValueAsString(map);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e);
}
}
and my assert in test now look like this:
mockMvc.perform(get(REST_URL))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(content().json(JsonUtil.writeIgnoreProps(USER, "registered")))
Thank you #dknaus for the detailed answer. Although this solution will not work in STRICT mode and checkJsonObjectKeysExpectedInActual method code needs to be replaced by following code [As suggested by #tk-gospodinov]:
for (String attribute : attributesToIgnore) {
expected.remove(attribute);
super.checkJsonObjectKeysExpectedInActual(prefix, expected, actual, result);
}

What security issues come from calling methods with reflection?

I'm working on a project that has hosts and clients, and where hosts can send commands to clients (via sockets).
I'm determined that using JSON to communicate works the best.
For example:
{
"method" : "toasty",
"params" : ["hello world", true]
}
In this example, when this JSON string is sent to the client, it will be processed and a suitable method within the client will be run as such:
public abstract class ClientProcessor {
public abstract void toasty(String s, boolean bool);
public abstract void shutdown(int timer);
private Method[] methods = getClass().getDeclaredMethods();
public void process(String data) {
try {
JSONObject json = new JSONObject(data);
String methodName = (String) json.get("method");
if (methodName.equals("process"))
return;
for (int i = 0; i < methods.length; i++)
if (methods[i].getName().equals(methodName)) {
JSONArray arr = json.getJSONArray("params");
int length = arr.length();
Object[] args = new Object[length];
for (int i2 = 0; i2 < length; i2++)
args[i2] = arr.get(i2);
methods[i].invoke(this, args);
return;
}
} catch (Exception e) {}
}
}
And using the ClientProcessor:
public class Client extends ClientProcessor {
#Override
public void toasty(String s, boolean bool) {
//make toast here
}
#Override
public void shutdown(int timer) {
//shutdown system within timer
}
public void processJSON(String json) {
process(json);
}
}
The JSON is sent by the server to the client, but the server could be modified to send different JSONs.
My questions are:
Is this a safe way of running methods by processing JSON?
Is there a better way to do this? I'm thinking that using reflection is terribly slow.
There's a 100 and 1 ways you can process a JSON message so that some processing occurs, but they'll all boil down to:
parse message
map message to method
invoke method
send response
While you could use a reflective call (performance-wise it would be fine for most cases) to invoke a method, that, imho, would be a little too open - a malicious client could for example crash your system by issuing wait calls.
Reflection also opens you up to having to correctly map the parameters, which is more complicated than the code you've shown in your question.
So don't use Reflection.
Would you could do is define a simple interface, implementations of which would understand how to process the parameters and have your processor (more commonly referred to as a Controller) invoke that, something like this:
public interface ServiceCall
{
public JsonObject invoke(JsonArray params) throws ServiceCallException;
}
public class ServiceProcessor
{
private static final Map<String, ServiceCall> SERVICE_CALLS = new HashMap<>();
static
{
SERVICE_CALLS.put("toasty", new ToastCall());
}
public String process(String messageStr)
{
try
{
JsonObject message = Json.createReader(new StringReader(messageStr)).readObject();
if (message.containsKey("method"))
{
String method = message.getString("method");
ServiceCall serviceCall = SERVICE_CALLS.get(method);
if (serviceCall != null)
{
return serviceCall.invoke(message.getJsonArray("params")).toString();
}
else
{
return fail("Unknown method: " + method);
}
}
else
{
return fail("Invalid message: no method specified");
}
}
catch (Exception e)
{
return fail(e.message);
}
}
private String fail(String message)
{
return Json.createObjectBuilder()
.add("status", "failed")
.add("message", message)
.build()
.toString();
}
private static class ToastCall implements ServiceCall
{
public JsonObject invoke(JsonArray params) throws ServiceCallException
{
//make toast here
}
}
}
Map method names to int constants and just switch(case) on these constants to invoke appropriate method.
"toasty" : 1
"shutdown": 2
switch()
case 1: toasty()
case 2: shutdown()
I believe you are trying to convert JSON string to Java object and vice versa... if that is the requirement then this would not be the right approach...
Try any open source API like Gson...
it is the API by Google for conversin of Java to JSON and vice versa.
Please check ...
https://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/Gson.html
Let me know if you have any further questions...

Categories

Resources