getJSON with ServiceStack? - json

Server Side:
[RestService("/x")]
public class XFilter
{
public long[] CountryIds { get; set; }
}
public class XService : RestServiceBase<XFilter>
{
private const int PageCount = 20;
public override object OnGet(XFilter request)
{
Debugger.Break(); // request is always default at this point !!
return a;
}
}
Client Side:
<script type="text/javascript">
var requestData= "{CountryIds:[1,2]}";
$.getJSON("/api/x", requestData, function (b) {
});
It must be so easy, but I could not get XFilter on server side with this approach.

This is not valid JSON:
var requestData= "{CountryIds:[1,2]}";
All strings must be quoted, e.g:
var requestData= '{"CountryIds":[1,2]}';
var requestData = JSON.stringify({CountryIds:[1,2]}); //same as above
This would be what you would send via $.ajax POST which you might consider doing since there is no convention to handle complex types in GET requests since it has to serialize it to the queryString.
If you wanted to send a complex type as a queryString you would need to construct the URL yourself. ServiceStack uses its JSV Format to handle complex types in GET requests (which is just JSON with CSV escaping, aka. JSON without quotes).
So your request would now be:
var requestData = "[1,2]";
$.getJSON("/api/x?CountryIds=" + requestData, function (b) {
});
Re-Cap: If you wanted to send JSON you would need to do so via an ajax POST.

var requestData= "{CountryIds:[1,2]}";
Must be
var requestData= {CountryIds:[1,2]};
!
But still there is a problem in getJSON. It does not work with arrays?
Send list/array as parameter with jQuery getJson is solution for it :)
Add this:
$.ajaxSetup({
traditional: true
});

Related

How can I send custom object from my callable Firebase Cloud Function in TypeScript to my Unity app?

I'm trying to use Firebase and its callable Cloud Functions for my Unity project.
With the docs and different posts I found on the web I struggle to understand how returning data works. (I come from Azure Functions in C#)
I use TypeScript, and try to return a custom object CharactersResponse:
export class CharactersResponse //extends CustomResponse
{
Code!: CharactersCode;
CharacterID?: string;
}
export enum CharactersCode
{
Success = 0,
InvalidName = 2000,
CharacterNameAlreadyExists = 2009,
NoCharacterSlotAvailable = 3000,
InvalidCharacterClass = 4000,
EmptyResponse = 9000,
UnknownError = 9999,
}
(Custom Response is a parent class with only an UnknownErrorMessage string property, that I use for adding extra message when needed, but only in Unity. I don't need it in my functions.)
I have the same in my C# Unity Project:
public class CharactersResponse : CustomResponse
{
public CharactersCode Code;
public string CharacterID;
}
public enum CharactersCode
{
Success = 0,
InvalidName = 2000,
CharacterNameAlreadyExists = 2009,
NoCharacterSlotAvailable = 3000,
InvalidCharacterClass = 4000,
EmptyResponse = 9000,
UnknownError = 9999,
}
I'm still learning but I found it useful to do this way for displaying correct messages in Unity (and also regarding localization).
When the Code is 0 (Success), I will usually need to get some data at the same time like in this example CharacterID, or CharacterLevel, CharacterName etc.. CharacterResponse will be used for all functions regarding Characters like "GetAllCharacters", "CreateNewCharacter" etc..
My Function (CreateNewCharacter) looks like this:
import * as functions from "firebase-functions";
import { initializeApp } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
import { CharactersResponse } from "./CharactersResponse";
import { CharactersCode } from "./CharactersResponse";
import { StringUtils } from "../Utils/StringUtils";
// DATABASE INITIALIZATION
initializeApp();
const db = getFirestore();
// CREATE NEW CHARACTER
export const CreateNewCharacter =
functions.https.onCall((data, context) =>
{
// Checking that the user is authenticated.
if (!context.auth)
{
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
}
// TEST
data.text = '';
// Authentication / user information is automatically added to the request.
const uid: string = context?.auth?.uid;
const characterName: string = data.text;
// Check if UserID is present
if (StringUtils.isNullOrEmpty(uid))
{
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'Missing UserID in Auth Context.');
}
const response = new CharactersResponse();
if (StringUtils.isNullOrEmpty(characterName))
{
response.Code = CharactersCode.InvalidName;
console.log("character name null or empty return");
return response; // PROBLEM IS HERE *****************
}
console.log("end return");
return "Character created is named : " + characterName + ". UID = " + uid;
});
In Unity, the function call looks like this:
private static FirebaseFunctions functions = FirebaseManager.Instance.Func;
public static void CreateNewCharacter(string text, Action<CharactersResponse> successCallback, Action<CharactersResponse> failureCallback)
{
Debug.Log("Preparing Function");
// Create the arguments to the callable function.
var data = new Dictionary<string, object>();
data["text"] = text;
// Call the function and extract the operation from the result.
HttpsCallableReference function = functions.GetHttpsCallable("CreateNewCharacter");
function.CallAsync(data).ContinueWithOnMainThread((task) =>
{
if (task.IsFaulted)
{
foreach(var inner in task.Exception.InnerExceptions)
{
if (inner is FunctionsException)
{
var e = (FunctionsException)inner;
// Function error code, will be INTERNAL if the failure
// was not handled properly in the function call.
var code = e.ErrorCode;
var message = e.Message;
Debug.LogError($"Code: {code} // Message: {message}");
if (failureCallback != null)
{
failureCallback.Invoke(new CharactersResponse()
{
Code = CharacterCode.UnknownError,
UnknownErrorMessage = $"ERROR: {code} : {message?.ToString()}"
});
}
}
}
}
else
{
Debug.Log("About to Deserialize response");
// PROBLEM IS HERE *********************
CharactersResponse response = JsonConvert.DeserializeObject<CharactersResponse>(task.Result.Data.ToString());
Debug.Log("Deserialized response");
if (response == null)
{
Debug.LogError("Response is NULL");
}
else
{
Debug.Log("ELSE");
Debug.Log($"Response: {response}");
Debug.Log(response.Code.ToString());
}
}
});
}
The problem :
In my Unity C# code, task.Result.Data contains the CharactersCode I've set in my function, but I can't find a way to convert it to CharactersResponse. (It worked in Azure Functions). Moreover, the line just after Deserialization Debug.Log("Deserialized response"); is not executed. The code seems stuck in the deserialization process.
I tried with and without extending my TypeScript class with CustomResponse(because I don't need it in my Function so I didn't extended it at first).
I also tried setting a CharacterID because I thought maybe it didn't like the fact that this property was missing but the result is the same.
I don't understand what is the problem here? If any of you can help.
Thanks.
HttpsCallableResult.Data is of type object!
=> Your ToString will simply return the type name something like
System.Object
or in your case the result is a dictionary so it prints out that type.
=> This is of course no valid JSON content and not what you expected.
Simply construct the result yourself from the data:
var result = (Dictionary<string, object>)task.Result.Data;
CharactersResponse response = new CharactersResponse
{
Code = (CharactersCode)(int)result["Code"],
CharacterID = (string)result["CharacterID"];
};
I wanted to implement derHugo's solution but couldn't find a way to convert task.Result.Data to Dictionary<string, object>.
The code was stuck at var result = (Dictionary<string, object>)task.Result.Data; even in step by step debugging and no error popped up.
OLD SOLUTION:
So I did a little research and stumbled upon this post and ended up using this instead :
var json = JsonConvert.SerializeObject(task.Result.Data);
CharactersResponse response = JsonConvert.DeserializeObject<CharactersResponse>(json);
I basically convert the task.Result.Data to JSON and convert it back to CharactersResponse and it works. I have what I wanted.
However, I seem to understand that it is not the best solution performance-wise, but for now it is okay and I can now move forward in the project, I'll try to find a better solution later.
NEW SOLUTION:
I wanted to try one last thing, out of curiosity. I wondered what if I convert to JSON at the beginning (in my function) instead of at the end (in my Unity app). So I did this in my function's TypeScript code:
response.Code = CharactersCode.InvalidName;
var r = JSON.stringify(response); // Added this line
return r; // return 'r' instead of 'response'
In my C# code, I retried this line of code:
CharactersResponse response = JsonConvert.DeserializeObject<CharactersResponse>(task.Result.Data.ToString());
And it works ! I just needed to convert my object to JSON in my function before returning it. It allows me to "save" one line of code to process on the client side compared to the old solution.
Thanks derHugo for your answer as it helped me finding what I want.

How to access JSON?

I am wrote API method, after calling that method , I got my response like
[
{
"spark_version": "7.6.x-scala2.12"
}
]
Now I want to have variable in my API method which store value 7.6.x-scala2.12.
API Controller method
[HttpGet]
public IActionResult GetTest(int ActivityId)
{
string StoredJson = "exec sp_GetJobJSONTest " +
"#ActivityId = " + ActivityId ;
var result = _context.Test.FromSqlRaw(StoredJson);
return Ok(result);
}
So how this variable should call on this response to get string stored in spark_version?
Thank you
As you have the JavaScript tag, here's how you'd do it in JS:
If you are able to call your API method, then you can just assign the response to a variable. For example, if you are calling it using the fetch API, it would look something like:
let apiResponse;
fetch('myApiUrl.com').then((response) => {
if (response.status === 200) {
apiResponse = response.body;
console.log('Response:', apiResponse[0]['spark_version']);
}
});
(I defined the variable outside the then to make it globally accessible)

Bad request AJAX post to Spring API

I'm sending JSON data to my Spring API but I always get a bad request. I have tried some things. At first, chanceReward was of type Map<String, Object>. Later I thought it should be a String but it still had a bad request. I researched and thought I needed consumes = "application/json" in the annotation but result is the same. Not sure anymore what to do. Below is the code for my API:
#RequestMapping(value = "/chance/{id}/saveChanceRewards", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
public #ResponseBody Map<String, Object> saveChanceRewards(#PathVariable("id") String id,
#RequestBody String chanceRewards) {
try {
JSONArray jsonArray = new JSONArray(chanceRewards);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject JObject = jsonArray.getJSONObject(i);
System.out.println(JObject.getString("name") + " " + JObject.getString("weight"));
}
} catch(JSONException e) {
_log.error("Error parsing JSON");
}
Map<String, Object> map = new HashMap<String, Object>();
// TODO
return map;
}
Below is the ajax code (inside a .jsp):
let arrayRewards = [];
// get the data from dynamic list of text fields
for (let i = 1; i <= chanceRewardCount; i++) {
arrayRewards.push({
name: $('#chanceRewardName' + i).val(),
weight: $('#chanceRewardWeight' + i).val()
});
}
let data = {'data': arrayRewards};
let jsonData = JSON.stringify(data);
$.ajax({
type: 'post',
dataType: "json",
data: data,
contentType: 'application/json',
url: "${home}/chance/${id}/saveChanceRewards",
method: 'post',
success: function(response) {
console.log('response', response);
},
error: function(err) {
console.log('error', err);
}
});
I'm using Spring Framework 3.2.1.
The 400 Bad Request error is an HTTP status code that means that the request you sent to the website server, often something simple like a request to load a web page, was somehow incorrect or corrupted and the server couldn't understand it.
That mean the server not able to understand the request from your ajax.
First, change #RequestBody String chanceRewards to #RequestBody ChanceRewards chanceRewards
And define ChanceRewards and ChanceReward class.
class ChanceReward {
private String name;
private String weight;
// Getter Setter ...
}
class ChanceRewards {
private List<ChanceReward> data;
// Getter Setter ...
}
If still failed, try open inspect mode and click network tab to check the request send from ajax.
Replace double quotes in your url: "${home}/chance/${id}/saveChanceRewards", by backtick.
There are quite a few things going on here, so let's work on them!
First, I see you've stringified the data into jsonData, but your actual ajax post has data: data instead. Easy fix, just swap in the right variable.
Second thing I notice is that you're wrapping the rewards array in an object (with data = {'data': arrayRewards}) but your Java code expects the array itself (JSONArray) right out of the request body. So this will also throw an exception. You don't have to wrap the array with an object if it's not needed.
Lastly, you mention that you always get a "bad request", but what exactly do you mean? An "HTTP 400" error? Some other HTTP error? It might be useful to give more info on the exact error(s) you see on the javascript side and on the Java server side.
All the other things like worrying about making a ChanceReward / ChanceRewards class, accepts/consumes/produces headers, etc., are superfluous at this point. They are boilerplate niceties and you don't need any of them for this to work correctly.

MVC Ajax GET method with JSON object as parameter

I'm trying to make an Ajax request to an action sending a Json object as parameter.
Here is the code of the Jquery:
var jsonObject = {
"Id": 33,
"Name": "TEST"
};
$.ajax({
type: "GET",
url: rootPath + "ScheduledEvents/Edit/",
data: JSON.stringify(jsonObject),
/*contentType: "application/json; charset=utf-8",*/
dataType: "json",
success: function (data) {
$("#defaultModalRightEventContent").html(data);
$("#defaultModalRightEvent").modal("show");
},
error: function (request, status, error) {
alert(request.responseText);
}
});
Here is the class on the server side:
public class TestingClass
{
public int Id { get; set; }
public string Name { get; set; }
}
And here is the controller action:
[HttpGet]
public ActionResult Edit(TestingClass testingClass)
{
return PartialView("_ScheduledEvent", new ScheduledEventVM());
}
What is happening is that even though the object isn't null when hitting the action, both Id and Name as empty.
If I change the method from GET to POST then the object arrives correctly to the action, but then I get a RequiredValidationToken error. And since I don't actually have a form with the token to use I'm really out of options.
Is there a way to use GET or do I really need to use POST.
Thanks in advance for your help.
Since your method reads Edit, it means it is changing the state of the server. Hopefully an update operation in the database.
As far as RequiredValidation Token error is concerned, I think you are talking about AntiForgeryToken. You can avoid this by removing [ValidateAntiForgeryToken] attribute on your post requests or you can provide the same token from client side using a helper method.
If you write Html.AntiForgeryToken() in your views, it will create a hidden field on in html with name __RequestVerificationToken. You can pick up this value and also push this along with ajax request's data and header. After doing this, your problem will go away.

Not able to read nested properties from doPost(e)

I am trying to create a webservice using the Contentservice in Apps Script and doPost(e) function to interact with Google Apps AdminDirectory service
Here is the overview of my code. I saved this as my server and published it as a websapp
function doPost(e) {
var data = e.parameter;
var resourceType = data.resourceType;
var method = data.method;
var resource = data.resource;
var resourceParams = resource.parameters;
//other code to work with AdminDIrectory
// return ContentService.createTextOutput(myoutputdata).setMimeType(ContentService.MimeType.JSON);
}
In my client code which I wrote using Apps Script to test the webservice
function test() {
var jsonData = {
authKey : 'HwZMVe3ZCGuPOhTSmdcfOqsl12345678',
resourceType : 'user',
method : 'get',
resource : {
parameters : {
userKey : 'waqar.ahmad#mydomain.com'
}
}
}
var url = 'https://script.google.com/macros/s/xyzabc12345678_wE3CQV06APje6497dyI7Hh-mQMUFM0pYDrk/exec';
var params = {
method : 'POST',
payload : jsonData
}
var resp = UrlFetchApp.fetch(url, params).getContentText();
Logger.log(resp);
}
Now when I try to read e.parameter.resource.parameters on server side, it gives error and shows that e.parameter.resource is string type.
How I can read nested objects on server side? It seems, it is recognizing only first level parameters.
Using
function doPost(e) {
return ContentService.createTextOutput(JSON.stringify(e.parameter)).setMimeType(ContentService.MimeType.JSON);
You get the response
"{"authKey":"HwZMVe3ZCGuPOhTSmdcfOqsl12345678","resource":"{parameters={userKey=waqar.ahmad#mydomain.com}}","method":"get","resourceType":"user"}"
so it looks like it is flattening any nests into a string. In the comments of a similar problem it is noted that the documentation for UrlFetchApp permits the payload to be "It can be a String, a byte array, or a JavaScript key/value map", but I assume this doesn't extend to nested key/value maps.
As noted in the other answer the solution would be to stringify the payload e.g.
var params = {
method : 'POST',
payload : {'data':JSON.stringify(jsonData)}
};
I had problems handling the payload just as a string which is why I used a key value
On the server side script you can handle with
function doPost(e) {
var data = JSON.parse(e.parameter.data);
var userKey = data.resource.parameters.userKey;
...
}
Maybe it arrives as JSON string? If that's the case you'd have to parse it back-end, something like:
resourceObj = JSON_PARSE_METHOD(e.parameter.resource);
for example in PHP this would be
$resourceObj = json_decode(e->parameter->resource);
You could find out if it's a JSON string by printing its value (or debugging it) on the backend.
Hope this helps. Cheers