EliasSoares's avatar

How to Validate JSON Input using Requests?

I'm creating an controller that should receive an JSON Input, and I wan't to validate it contents using Validator Rules and Requests...

It's possible out of the box, or I will need to extend the FormRequest class?

Thank's in advance,

0 likes
14 replies
topvillas's avatar

Quick thought! Could you use json_decode and run that through a validator?

EliasSoares's avatar

@topvillas

Yes, I could, but I want to keep using Request pattern. I'm currently extending Laravel FormRequest class to an JsonRequest class that accepts JSON directly, so the only difference will be using JsonRequest instead of FormRequest.

EliasSoares's avatar
EliasSoares
OP
Best Answer
Level 10

Just to help who may need this:

To accept an Json as input for an Request, you can replace FormRequest by this JsonRequest class that I made:

<?php namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

abstract class JsonRequest extends FormRequest {

    /**
     * Get the validator instance for the request.
     *
     * @return \Illuminate\Validation\Validator
     */
    protected function getValidatorInstance()
    {
        $factory = $this->container->make('Illuminate\Validation\Factory');

        if (method_exists($this, 'validator'))
        {
            return $this->container->call([$this, 'validator'], compact('factory'));
        }
        return $factory->make(
            $this->json()->all(), $this->container->call([$this, 'rules']), $this->messages(), $this->attributes()
        );
    }
}
8 likes
werewolf81's avatar

Hi! @EliasSoares Your solution is exactly what I need in my application, however, I am not quite sure which files you have changed and placed the code you mentioned above?

Also, where and when you tell the framework to decode JSON and pass decoded to the request?

Have you also found out how to validate individual requests? I mean:

  1. if JSON is valid
  2. if particular fields in that JSON match rules defined somewhere.

Thanks!

bethany's avatar

@werewolf81 Just put EliasSoares' JsonRequest class under the app/Http/Requests folder and then when you create your custom FormRequest class, extend JsonRequest rather than Request. Then all the rules can be applied as usual to any json attributes you're sending. Works perfectly.

2 likes
fureszpeter's avatar

You can create a JsonApiMiddleware something like this:

<?php

namespace App\Http\Middleware;

use Closure;

class JsonApiMiddleware
{
    const PARSED_METHODS = [
        'POST', 'PUT', 'PATCH'
    ];

    /**
     * Handle an incoming request.
     *
     * @param \Illuminate\Http\Request $request
     * @param \Closure $next
     *
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (in_array($request->getMethod(), self::PARSED_METHODS)) {
            $request->merge((array)json_decode($request->getContent()));
        }

        return $next($request);
    }
}

use this middleware in your route:

Route::group(
    [
        'prefix'     => 'api',
        'middleware' => ['JsonApiMiddleware']
    ], function () {

    Route::resource('listing', 'ListingController');

});

And you can use the regular way in your Controller:

    /**
     * Store a newly created resource in storage.
     *
     * @param Request $request
     *
     * @return Response
     */
    public function store(Request $request)
    {
        /** @var \Illuminate\Contracts\Validation\Validator $validation */
        $validation = Validator::make(
            $request->all(),
            [
                'name' => 'required|numeric'
            ]
        );

        if ($validation->fails()) {
            dd($validation->getMessageBag()->all());
        }
17 likes
zainescu's avatar

In the @fureszpeter solution instead of:

$request->merge((array)json_decode($request->getContent()));

you may want to use:

 $request->merge(json_decode($request->getContent(),true));

Casting will convert only first level into array and deeper nested json data will be objesct so validators such as "array.field" would fail.

7 likes
mitchellmckenna15's avatar

Instead of overriding getValidatorInstance() as @EliasSoares suggested, you can instead just override validationData():

abstract class JsonRequest extends FormRequest {
    /**
     * The data to be validated should be processed as JSON.
     * @return mixed
     */
    protected function validationData()
    {
        return $this->json()->all();
    }
}
10 likes
JoshP's avatar

I have my various Request classes extend ApiRequest

class ApiRequest extends FormRequest
{
    public function expectsJson()
    {
        return true;
    }
}

This overrides a method in the InteractsWithContentTypes trait, used by Illuminate\Http\Request, extended by Illuminate\Foundation\Http\FormRequest extended in my class above.

using v5.4

polarcubs's avatar

Hi some questions. Do I still need to add this JsonApiMiddleware in Laravel 5.5?

Should I add an array cast @fureszpeter or not as suggested by @zainescu ?

amfischer's avatar

@polarcubs Laravel 5.5 has mime type validation and I think you can use json as one of the options.

Though I'm having trouble getting this to work. I've tried send through as a json string and using json_decode before hitting validation but my upload always fails w error message saying incorrect file type.

Here is my code if anyone can help

public function uploadMaps(Request $request)
{   
    $this->validate($request, 
        ['polygons' => 'required|mimetypes:application/json']);
}

I've also tried

public function uploadMaps(Request $request)
{
    $this->validate($request,
        ['polygons' => 'required|mimes:json']);
}

If I dd($request->polygons) before validation the mime type clearly shows mimeType: application/json so I'm not sure what's going on.

ajitdas's avatar

Hi, this seems be a dead thread yet, i am having same problem i tried above solutions

@EliasSoares , solution giving error "message": "Target [App\\Http\\JsonRequest] is not instantiable.",

I also tried @fureszpeter solution but it doesnt work,

I am using laravel 5.5 could it be that some of those functions has been removed for newer version? If so what could be solution to this problem for new laravel

koli's avatar

if u need a nested validation, like this raw request:

{
    'address':{
        'province':{'id'=10},
        'city':{'id'=1},
        'street':"fake street 123",
        'postal_code':"asdasd",
        'phone':"asdasd"
    }
}

this may work:

public function yourFunctionName(Request $request)
{
     $rules = [
                    'address.province.id'=>'required|exists:locations,id',
                    'address.city.id'=>'required|exists:locations,id',
                    'address.street'=>'required|string',
                    'address.postal_code'=>'required|string',
                    'phone'=>'required|string',
                ];
        
        $this->validate($request, $rules);
    
}

Please or to participate in this conversation.