Asserting object is a valid top level json serializable - java

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);
}
}

Related

Is there a possibility to store the single event information at a time in a JSONObject/JsonNode using the Jackson JsonParser

I am trying to read the events from a large JSON file one-by-one using the Jackson JsonParser. I would like to store each event temporarily in an Object something like JsonObject or any other object which I later want to use for some further processing.
I was previously reading the JSON events one-by-one and storing them into my own custom context: Old Post for JACKSON JsonParser Context which is working fine. However, rather than context, I would like to store them into jsonObject or some other object one by one.
Following is my sample JSON file:
{
"#context":"https://context.org/context.jsonld",
"isA":"SchoolManagement",
"format":"application/ld+json",
"schemaVersion":"2.0",
"creationDate":"2021-04-21T10:10:09+00:00",
"body":{
"members":[
{
"isA":"student",
"name":"ABCS",
"class":10,
"coaching":[
"XSJSJ",
"IIIRIRI"
],
"dob":"1995-04-21T10:10:09+00:00"
},
{
"isA":"teacher",
"name":"ABCS",
"department":"computer science",
"school":{
"name":"ABCD School"
},
"dob":"1995-04-21T10:10:09+00:00"
},
{
"isA":"boardMember",
"name":"ABCS",
"board":"schoolboard",
"dob":"1995-04-21T10:10:09+00:00"
}
]
}
}
At a time I would like to store only one member such as student or teacher in my JsonObject.
Following is the code I have so far:
What's the best way to store each event in an Object which I can later use for some processing.
Then again clear that object and use it for the next event?
public class Main {
private JSONObject eventInfo;
private final String[] eventTypes = new String[] { "student", "teacher", "boardMember" };
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException, JAXBException, URISyntaxException {
// Get the JSON Factory and parser Object
JsonFactory jsonFactory = new JsonFactory();
JsonParser jsonParser = jsonFactory.createParser(new File(Main.class.getClassLoader().getResource("inputJson.json").toURI()));
JsonToken current = jsonParser.nextToken();
// Check the first element is Object
if (current != JsonToken.START_OBJECT) {
throw new IllegalStateException("Expected content to be an array");
}
// Loop until the start of the EPCIS EventList array
while (jsonParser.nextToken() != JsonToken.START_ARRAY) {
System.out.println(jsonParser.getCurrentToken() + " --- " + jsonParser.getCurrentName());
}
// Goto the next token
jsonParser.nextToken();
// Call the method to loop until the end of the events file
eventTraverser(jsonParser);
}
// Method which will traverse through the eventList and read event one-by-one
private static void eventTraverser(JsonParser jsonParser) throws IOException {
// Loop until the end of the EPCIS events file
while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
//Is there a possibility to store the complete object directly in an JSON Object or I need to again go through every token to see if is array and handle it accordingly as mentioned in my previous POST.
}
}
}
After trying some things I was able to get it working. I am posting the whole code as it can be useful to someone in the future cause I know how frustrating it is to find the proper working code sample:
public class Main
{
public void xmlConverter (InputStream jsonStream) throws IOException,JAXBException, XMLStreamException
{
// jsonStream is the input JSOn which is normally passed by reading the JSON file
// Get the JSON Factory and parser Object
final JsonFactory jsonFactory = new JsonFactory ();
final JsonParser jsonParser = jsonFactory.createParser (jsonStream);
final ObjectMapper objectMapper = new ObjectMapper ();
//To read the duplicate keys if there are any key duplicate json
final SimpleModule module = new SimpleModule ();
module.addDeserializer (JsonNode.class, new JsonNodeDupeFieldHandlingDeserializer ());
objectMapper.registerModule (module);
jsonParser.setCodec (objectMapper);
// Check the first element is Object if not then invalid JSON throw error
if (jsonParser.nextToken () != JsonToken.START_OBJECT)
{
throw new IllegalStateException ("Expected content to be an array");
}
while (!jsonParser.getText ().equals ("members"))
{
//Skipping the elements till members key
// if you want you can do some process here
// I am skipping for now
}
// Goto the next token
jsonParser.nextToken ();
while (jsonParser.nextToken () != JsonToken.END_ARRAY)
{
final JsonNode jsonNode = jsonParser.readValueAsTree ();
//Check if the JsonNode is valid if not then exit the process
if (jsonNode == null || jsonNode.isNull ())
{
System.out.println ("End Of File");
break;
}
// Get the eventType
final String eventType = jsonNode.get ("isA").asText ();
// Based on eventType call different type of class
switch (eventType)
{
case "student":
final Student studentInfo =
objectMapper.treeToValue (jsonNode, Student.class);
//I was calling the JAXB Method as I was doing the JSON to XML Conversion
xmlCreator (studentInfo, Student.class);
break;
case "teacher":
final Teacher teacherInfo =
objectMapper.treeToValue (jsonNode, Teacher.class);
xmlCreator (teacherInfo, Teacher.class);
break;
}
}
}
//Method to create the XML using the JAXB
private void xmlCreator (Object eventInfo,
Class eventType) throws JAXBException
{
private final StringWriter sw = new StringWriter ();
// Create JAXB Context object
JAXBContext context = JAXBContext.newInstance (eventType);
// Create Marshaller object from JAXBContext
Marshaller marshaller = context.createMarshaller ();
// Print formatted XML
marshaller.setProperty (Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
// Do not add the <xml> version tag
marshaller.setProperty (Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
// XmlSupportExtension is an interface that every class such as Student Teacher implements
// xmlSupport is a method in XmlSupportExtension which has been implemented in all classes
// Create the XML based on type of incoming event type and store in SW
marshaller.marshal (((XmlSupportExtension) eventInfo).xmlSupport (),
sw);
// Add each event within the List
eventsList.add (sw.toString ());
// Clear the StringWritter for next event
sw.getBuffer ().setLength (0);
}
}
This is the class that overrides the JACKSON class.
This can be used if your Json has duplicate JSON keys. Follow this post for the complete explnation if you need. If you dont need then skip this part and remove the part of the code module from the above class:
Jackson #JsonAnySetter ignores values of duplicate key when used with Jackson ObjectMapper treeToValue method
#JsonDeserialize(using = JsonNodeDupeFieldHandlingDeserializer.class)
public class JsonNodeDupeFieldHandlingDeserializer extends JsonNodeDeserializer {
#Override
protected void _handleDuplicateField(JsonParser p, DeserializationContext ctxt, JsonNodeFactory nodeFactory, String fieldName,
ObjectNode objectNode, JsonNode oldValue, JsonNode newValue) {
ArrayNode asArrayValue = null;
if (oldValue.isArray()) {
asArrayValue = (ArrayNode) oldValue;
} else {
asArrayValue = nodeFactory.arrayNode();
asArrayValue.add(oldValue);
}
asArrayValue.add(newValue);
objectNode.set(fieldName, asArrayValue);
}
}

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.

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);
}

Gson parsing "null" objects as Array/Object?

I have a return from the server which can come through as:
[{
"id":"1",
"objectOne": {
"name":"jim"
}
}, {
"id":"1",
"objectOne": [{
"name": "jim1"
}, {
"name": "jim2"
}
}, {
"id":"1",
"objectOne": null
}]
That is, one value can either be an object, an object array, or null.
I'm using Gson converter with Retrofit and I'm using this TypeAdapterFactory to force single objects to be read as an array:
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapterFactory(new ObjectToArrayFactory());
Gson gson = gsonBuilder.create();
Factory:
private class ObjectToArrayAdapter<T> extends TypeAdapter<List<T>> {
Gson gson;
private Class<T> adapterclass;
public ObjectToArrayAdapter(Gson gson, Class<T> adapterclass) {
this.gson = gson;
this.adapterclass = adapterclass;
}
#Override
public void write(JsonWriter out, List<T> value) throws IOException {}
public List<T> read(JsonReader reader) throws IOException {
List<T> list = new ArrayList<T>();
if (reader.peek() == JsonToken.BEGIN_OBJECT) {
// If it's meant to be an array and instead it's a single object, add it to a newly created list.
parseObject(list, reader, gson);
} else if (reader.peek() == JsonToken.BEGIN_ARRAY) {
// Otherwise, if it is actually a list, manually parse each item and add it to the list
parseArray(list, reader, gson);
} else if(reader.peek() == JsonToken.NULL) {
// However if the server gives a null object, just return null.
return null;
}
return list;
}
private void parseArray(List<T> list, JsonReader reader, Gson gson) throws IOException {
reader.beginArray();
while (reader.hasNext()) {
parseObject(list, reader, gson);
}
reader.endArray();
}
private void parseObject(List<T> list, JsonReader reader, Gson gson) throws IOException {
T inning = gson.fromJson(reader, adapterclass);
list.add(inning);
}
}
My problem is that, when I ask Retrofit to parse the value as an Array:
private List<PaymentsOption> objectOne;
The Gson parser seems to get confused, when it get's to the section of the json which looks like this:
"objectOne": null
I've debugged and logged my way through the parsing and it seems it follows what amounts to this code path (For brevity, I've parse out the actual code):
if(reader.peek() == JsonToken.BEGIN_ARRAY) {
reader.beginArray();
while(reader.hasNext()) { // public void parseTag()
if(reader.peek() == JsonToken.BEGIN_OBJECT) {
T inning = gson.fromJson(reader, adapterclass); <-- Crashes here
}
}
reader.endArray();
}
So, it shouldn't be "peeking" as a beginArray as it's "null". It also shouldn't allow a reader.beginArray() as it's still "null". It should peek again and see beginObject. It allows a reader.beginObject() inside of gson.fromJson but fails on reader.readName() as it's actually reading "null". Exception is as follows:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a name but was NULL at line 24 column 39 path $[1].objectOne
10-27 12:05:20.452 E/Exception: at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:200)
10-27 12:05:20.452 E/Exception: at com.google.gson.Gson.fromJson(Gson.java:810)
10-27 12:05:20.452 E/Exception: at uk.co.utils.network.ObjectToArrayFactory$ObjectToArrayAdapter.parseTag(ObjectToArrayFactory.java:70)
I don't understand why the reader.peek() is showing first a beginArray, allowing a reader.beginArray(), then showing a reader.peek() as a beginObject() and why it's allowing a reader.beginObject(). As far as I understand, it should have shown a reader.peek() == Json.Token.NULL ...?
You need to write a TypeAdapter and register that when you are building your gson object. In your adapter's read method you can check whether the given parameter is either null or not, or empty and take action accordingly.Your read method will look like:
public Number read(JsonReader in) throws IOException{
if(in.peek() == JsonToken.NULL) in.nextNull();
try{
//read value and take suitable action
}catch(Exception e){}
}
But you need to write a typeAdapter for every different data type that needs special treatment.

Java JSON serialization - best practice

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
}
}

Categories

Resources