Click here to Skip to main content
15,880,608 members
Articles / Web Development / Spring

Validating Incoming JSON using Upida/Jeneva

Rate me:
Please Sign up or sign in to vote.
5.00/5 (6 votes)
24 Nov 2015CPOL7 min read 28.3K   177   7   2
Web development using JSON is simple

Introduction

In the previous article, I described how Jeneva helps in solving common problems in implementing Spring Mvc-based web applications. This article shows how Jeneva can also greatly simplify your validation routines.

*Note, this article assumes that you have gone through my previous article.

Background

If you apply the techniques from my previous article in your web application, it can greatly reduce the amount of custom coding. But there is still one very important and useful feature in Jeneva - validation.

The implementation is pretty simple. First of all, you have to identify classes that require validation, usually these are domain classes. Secondly, you have to identify validation methods for each class - for example, class Client has two methods - validation before save and validation before update. This means that the same class - Client can be validated in two different ways - for save and for update. Sometimes, you may require different validation methods - for example, Assign or Merge or whatever. And the last step is the actual implementing of these validation methods. For example, the Client class must have two validation methods - validateForSave() and validateForUpdate().

Implementing

Let's create these validation methods for the Client class. In order to follow all the SOLID principles, I will create a separate class - ClientValidator which will contain these validation methods. The main idea of the Jeneva-based validation is the following - You must create a new instance of the JenevaValidationContext class every time you need to validate something. Every time you find an error, you must register it in the context instance using its methods. Using the context instance ensures that each error message is tied with the corresponding property path. By the way, the context class already contains several simple validation routines, for example, it can check whether a particular field is null, or if it is present in JSON, it can check text length or collection's size, it can check regular expressions, etc. As you must know, Jeneva manages data deserialization, and it stores information about each JSON field, therefore, later you can validate if a field was present in JSON, if it was null or if it was correctly parsed during deserialization. This information is accessible through the JenevaValidationContext class methods.

One the main goals of the JenevaValidationContext class is to keep track of the property path. For example, when you validate an object and then validate its children, the context class ensures that all error messages are connected with corresponding property paths. The result of validation is a list of failures, where a failure is a property path text and a message. This failure structure is serialized to JSON and sent back to browser, where it is parsed and displayed correctly in correct places in HTML.

The best practice would be deriving from the JenevaValidationContext class, extending some additional application-specific validation routines there and then using the subclass in the validation methods. Below is the example of how to extend the context class:

Java
public class ValidationContext extends JenevaValidationContext implements IValidationContext {

	public ValidationContext(IJenevaContext context) {
		super(context);
	}

	@Override
	public void required() {

		this.assigned("is required");
		if (this.isFieldValid() && this.isValidFormat())
		{
			this.notNull("is required");
		}
	}

	@Override
	public void missing() {

		this.notAssigned("must be empty");
	}

	@Override
	public void number() {

		this.validFormat("must be valid number");
	}

	@Override
	public void date() {

		this.validFormat("must be valid date");
	}

	@Override
	public void floating() {

		this.validFormat("must be valid floating point number");
	}

	@Override
	public void email() {

		this.regexp("\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b", "must be valid Email");
	}

	@Override
	public void text() {

		this.stringLengthBetween(3, 20, "must be between 3 and 20 characters");
	}

	@Override
	public void trueFalse() {

		this.validFormat("must be 'yes' or 'no'");
	}
} 

The logic above is extremely helpful and will make the validation methods extremely simple and readable. For example, I will no longer have to write redundant error messages for strings between 3 and 20 characters, I will use text(). The same is for dates, doubles, numbers, required fields, etc.

In the following piece of code, you can see how validation method for Save is implemented validateForSave():

Java
@Service
public class ClientValidator implements IClientValidator {

	@Autowired
	public ILoginValidator loginValidator;

	@Override
	public void validateForSave(Client target, IValidationContext context) {

		context.setField("id", target.getId());    // validate id property now
		context.missing();  // it must be missing - not present in json
		
		context.setField("name", target.getName());   // validate name property now
		context.required();    // it is required - present in json and not null
		context.text();    // it also must be between 3 and 20 characters length
		
		context.setField("lastname", target.getLastname());
		context.required();
		context.text();
		
		context.setField("age", target.getAge());
		context.required();
		context.number();
		context.greaterThan(0, "must be greater than zero");
		
		context.setField("logins", target.getLogins());
		context.required();
		context.countBetween(1, 5, "must be at least one login");
		
		context.addNested();   //  let us validate child properties - logins
		int index = 0;
		for (Login login : target.getLogins())
		{
			context.setIndex(index++);    // the logins - is indexed 
                     // property - property path will have index - logins[4].name
			context.setTarget(login);     // validate login class
			this.loginValidator.validateForSave(login, context);
		}

		context.removeNested();  // switch back - up in property path - back to 
                                  // Client's properties
	}
}

The setField() method tells the context what field is currently validated, i.e., if you register a failure in the context, it will have property path of the current field. Validation routines must go after the setField() method call. The JenevaValidationContext class contains numerous validation routines. These routines usually do two actions: first - they check a condition (for example, check if field value is null), and second - they register a failure if the condition is not true (using the fail() method). For example, assigned() - checks if field is assigned (is present in JSON) and if false - it registers a failure using the current property path; notNull(), isNull() are selfdescriptive; validFormat() - registers a failure if a field value is not correctly parsed (integer or double).

As you have noticed the JenevaValidationContext class, besides the validation routines methods, also contains other important methods: setTarget() sets up a current validated object, this method is important and should always be called before any validation routine; addNested() - this method propogates current property in the property path as nested object, all subsequent calls to setField() will result in concatenating property name with the nested object name; the removeNested() method does the inverse; the setIndex() method also adds indexing to the current property path - "[" + index + "]".

Here, you can see validation methods for the Login class.

Java
@Service
public class LoginValidator implements ILoginValidator {

	@Override
	public void validateForSave(Login target, IValidationContext context) {

		context.setField("id", target.getId());
		context.missing();
		
		context.setField("name", target.getName());
		context.required();
		context.text();
		
		context.setField("password", target.getPassword());
		context.required();
		context.text();
		
		context.setField("enabled", target.getEnabled());
		context.trueFalse();
		
		context.setField("client", target.getClient());
		context.missing();
	}
} 

When the validators for all the domain and DTO classes are done, we are free to define a facade class which will be injected into our services or controllers and which will be used to fire validation. Here is an example of the validation facade:

Java
@Service
public class ValidationFacade implements IValidationFacade {

	@Autowired
	public IValidationContextFactory contextFactory;

	@Autowired
	public IClientValidator clientValidator;

	// @Autowired
	// other validators

	@Override
	public void assertClientForSave(Client target) {

		IValidationContext context = this.contextFactory.getNew();
		context.setTarget(target);
		this.clientValidator.validateForSave(target, context);
		context.assertValid();
	}

	@Override
	public void assertClientForUpdate(Client target) {

		IValidationContext context = this.contextFactory.getNew();
		context.setTarget(target);
		this.clientValidator.validateForUpdate(target, context);
		context.assertValid();
	}
}

The class looks pretty simple, here I use simple factory method to get a new instance of the validation context. The most important is the last line of each method - assertValid() - this method throws ValidationException if at least one error is registered in the context, otherwise it does nothing.

Here, you can see how the facade is injected and used in the service layer:

Java
@Service
public class ClientService implements IClientService {

	@Autowired
	public IMapper mapper;

	@Autowired
	public IValidationFacade validator;

	@Autowired
	public IClientDao clientDao;


	@Override
	public void save(Client item) {
		this.validator.assertClientForSave(item);
		this.mapper.map(item, Client.class);
		this.clientDao.save(item);
	}

	@Override
	public void update(Client item) {
		this.validator.assertClientForUpdate(item);
		Client existing = this.clientDao.load(item.getId());
		this.mapper.mapTo(item, existing, Client.class);
		this.clientDao.merge(existing);
	}
}

By the way, the validation context class is quite handy and easy to extend and use it differently. You can always play with its methods and get a different behavior. In the example code, you will see how it is used for validation in different circumstances - for example, validation before Delete. Please see the assertClientExists() and the assertMoreThanOneClient() methods in the ValidationFacade class.

Java
@Override
public void assertClientExists(Client item) {

    if (item == null)
    {
        IValidationContext context = this.contextFactory.getNew();
        context.fail("Client does not exist");
        context.assertValid();
    }
}

@Override
public void assertMoreThanOneClient(long count) {

    if (count == 1)
    {
        IValidationContext context = this.contextFactory.getNew();
        context.fail("Cannot delete the only client");
        context.assertValid();
    }
}

In these methods, you don't rely on a target validated object. You just have to assert that some condition is met. In this case, a ValidationException is thrown with just one failure in it, and property path is empty.

And that is it. Now validation must work. If validation succeeds, nothing happens. If validation fails, ValidationException is thrown. ValidationException contains a list of name-value pairs - property path and error message. In order to process this exception properly in Spring MVC, I will create a method in my controller, and mark it with @ExceptionHandler annotation. This technique is common in Spring MVC, while working with exceptions. Here is an implementation of this method.

Java
@ExceptionHandler
@ResponseBody
public FailResponse handleError(Exception ex, HttpServletResponse response) {

    FailResponse fail = null;
    response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
    if(ex instanceof ValidationException) {
        fail = ((ValidationException) ex).buildFailResponse();
    }
    else {
        fail = new FailResponse(ex.getMessage());
    }

    return fail;
}

I replace the HTTP response content with my JSON array of (property path - error message) pairs - FailResponse class.

Now, every time the ValidationException is thrown, it will be handled by the handleError() method, and a list of property paths and error messages will be sent back as JSON in the HTTP response.

Front-end

The last step is displaying those messages in the correct place in HTML. You are free to write your custom JavaScript for AngularJS or KnockoutJS, or anything else. Jeneva comes with a small JavaScript library for AngularJS, which makes it simpler displaying those errors. For ex. if you use angular, your HTML must look like this:

HTML
<label>Name:</label>
<input name="name" type="text" ng-model="name" jv-path="name" />
<span class="error" ng-if="form.name.$error.jvpath" 
ng-repeat="msg in form.name.$jvlist">{{msg}}</span>

The jVpath directive works the same way as any AngularJS validation directives, i.e., it tells that the name textbox is responsible for the "name" property path. The span below will repeatedly display the validation messages assigned to the "name" property path.

You can find out how to create a Single-Page Application (SPA) using AngularJS in my next article: AngularJS single-page app and Jeneva.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionBean Validation Pin
sunand8525-Nov-15 17:45
sunand8525-Nov-15 17:45 
AnswerRe: Bean Validation Pin
vladimir husnullin25-Nov-15 21:24
vladimir husnullin25-Nov-15 21:24 
Hi Sunand,

It is all different.
- If for example an integer field failed to be parsed (field = "ABC") - it will not throw exception in jeneva. It will record it as "wrong_format_field", and you would be able to validate it by calling validFormat() or isValidFormat() and provide proper error message at server side.
  • If for example field was populated in JSON as null - you would be able to check if it is null and also to check if it was populated (by calling isAssigned() or assigned()). If the same field was not populated at all (i.e. it was not part of JSON at all), you can check it as well (isAssigned() or assigned()) and provide valid error message.
Jeneva keeps track of all fields present in JSON, and keeps track of all fields that failed to be parsed.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.