I have a JSON response I get from backend which I'm displaying as {{ response | json }}. There's a copy to clipboard option where i need to copy the contents of response. I have the following code
copy(response){
let val = response;
const selBox = document.createElement('textarea');
selBox.style.position = 'fixed';
selBox.style.left = '0';
selBox.style.top = '0';
selBox.style.opacity = '0';
selBox.value = val;
document.body.appendChild(selBox);
selBox.focus();
selBox.select();
document.execCommand('copy');
document.body.removeChild(selBox);}
This copies as [object object] since response is an object. I can copy it converting the response to a string as let val = JSON.stringyfy(response) . But this does not copy it in a formatted way I display it, instead copies the json in one single line like a string. So how to copy to clipboard a JSON object in a proper formatted way?
There is a built-in Clipboard class in the angular cdk that makes this a little easier to do. You should also use the space parameter with your JSON.stringify
First npm install the #angular/cdk package if you don't have it already.
In your #NgModule import ClipboardModule
import { ClipboardModule } from '#angular/cdk/clipboard';
#NgModule({
imports: [
ClipboardModule
],
})
In your component's typescript file import the Clipboard class
import { Clipboard } from '#angular/cdk/clipboard';
#Component({ /* ... */ })
export class MyComponent {
constructor(private clipboard: Clipboard) { }
public copy() {
// replace this object with your data
const object = {abc:'abc',xy:{x:'1',y:'2'}}
// Note the parameters
this.clipboard.copy(JSON.stringify(object, null, 2));
}
}
In your component's template
<button (click)="copy()">
Copy Data
</button>
The result of stringifying {abc:'abc',xy:{x:'1',y:'2'}} when pasted:
{
"abc": "abc",
"xy": {
"x": "1",
"y": "2"
}
}
With reference to the answer linked by x4rf41, you can make your stringify function whitespace your JSON with let val = JSON.stringify(response,null,2). If you want syntax highlighting, you can use user123444555621's function.
A much neater way to copy text is to add an event listener for the copy event, and set the clipboardData dataTransfer object:
window.addEventListener('copy', (event) => {
if(copying){
let val = JSON.stringify(response,null,2);
event.preventDefault(); //stop the browser overwriting the string
event.clipboardData.setData("text/plain",val); //encode the appropriate string with MIME type "text/plain"
copying = false;}
});
copy = function (){
copying = true;
document.execCommand('copy');}
If you are using the afore-mentioned syntax highlighting function, you probably want to specify MIME type "text/html". Hopefully the formatting options in the linked answer suit your needs.
Related
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.
I want to access json with a value in input.
My function and json
import pet3 from '../../utils/pet3' //my json file
const getValueFromJson = (value) => {
const data = pet3
console.log(data.components) //it works fine
console.log(data.value) // it is undefined
}
getValueFromJson("components")
You can access the object keys dynamically by using the square brackets:
import pet3 from "../../utils/pet3"; //my json file
const getValueFromJson = (value) => {
const data = pet3;
console.log(data[value]);
};
getValueFromJson("components");
Edit:
Alternately, you can install and use a 3rd party library like lodash which provides the _.get() method that can be used like this:
import get from "lodash/get"; // if not installed, run `npm install lodash --save-dev`
const getValueFromJson = (value) => {
const data = pet3;
console.log(get(data, value, "default value")); /* returns "default value" if key is undefined. */
};
getValueFromJson("components.filename");
I have a JSON object in my assets folder I am reading as below
data.json
[
{
"modName":"deployment",
"year":"1992",
"description":"basic deployment"
},
{
"modName":"Integration",
"year":"1995",
"description":"popular integration"
}
]
In my dataService.ts file my reading JSON object
getData(){
return this.http.get('assests/data.json');
}
getDataInfo{
return this.getData().subscribe(data=>{
if(data!==undefined)
for(let i in data){
if(data[i].modName=='deployment'){
return data[i];
}
}
})
}
I am accessing this object in my main component
let data = this.dataService.getDataInfo()
this is giving me a subscriber though I want a specific data object.How can I retrieve it?
getData goes in service, getDataInfo goes in component and then in component you can create variable myDataInfo and just on line where you return data do this
this.myDataInfo = data[i]
OR
same as above with service and component method, but you can store your data to some variable eg. this.myJsonData
and then you can do something like this
get myDataInfo() {
return this.myJsonData.filter((item) => item.modName=='deployment')[0]
}
and the you just can use that object in your template easy.
I have a Component that has an injected service, which makes an Ajax call. I can receive the JSON data successfully and can dump it into the console after the promise "THEN" returns.
Here's my component. I can see the dumped data, but how do I set the component properties with that JSON and have it accessible in the template? Also, why can't I use "this.get" in my function below?
import Ember from 'ember';
export default Ember.Component.extend({
attr_types: Ember.inject.service('svc-attrtypes'),
atype_list: [],
actions: {
getATypes: function() {
this.get('attr_types').getTypes().then(function(json){
console.log(json);
this.atype_list = json;
console.log(this.atype_list);
// below returns: TypeError: this.get is not a function
this.get('atype_list').pushObjects(json);
});
}
}
});
In my template I have this:
{{#each atype_list.alltypes as |a|}}
<li>{{a.attr_type}} - {{a.attr_type_desc}}</li>
{{/each}}
If I manually place my JSON into the atype_list it shows perfectly on the template. But if I try to set it after my Ajax returns, nothing shows, except for in the console output.
I appreciate any help. I am sure I a missing something simple. ( or more likely, I'm just going about this all wrong)
This changed with anonymous function passed to then. You have to save this or use es6 arrow function syntax.
import Ember from 'ember';
const { service } = Ember.inject;
export default Ember.Component.extend({
attrTypes: service('svc-attrtypes'),
atypeList: [],
actions: {
// es6 version
getATypes(){
this.get('attrTypes').getTypes().then(array => {
this.set('atypeList', array); //replaces original array
this.get('atypeList').pushObjects(array); // adds array's elements to the end
});
}
// es5 version
getATypes: function () {
var _this = this;
this.get('attrTypes').getTypes().then(function(array){
_this.set('atypeList', array);
}
}
}
});
You wrote that you are new to ember, so I added little more syntax sugar. Also check ember-cli if you don't know about that already.
How to get a local big json data?
I have tried this, but I had no success:
var sa = require("./shared/resources/sa.json");
var array = new observableArrayModule.ObservableArray(sa);
Use the file-system module to read the file and then parse it with JSON.parse():
var fs = require('file-system');
var documents = fs.knownFolders.currentApp();
var jsonFile = documents.getFile('shared/resources/sa.json');
var array;
var jsonData;
jsonFile.readText()
.then(function (content) {
try {
jsonData = JSON.parse(content);
array = new observableArrayModule.ObservableArray(jsonData);
} catch (err) {
throw new Error('Could not parse JSON file');
}
}, function (error) {
throw new Error('Could not read JSON file');
});
Here's a real life example of how I'm doing it in a NativeScript app to read a 75kb/250 000 characters big JSON file.
TypeScript:
import {knownFolders} from "tns-core-modules/file-system";
export class Something {
loadFile() {
let appFolder = knownFolders.currentApp();
let cfgFile = appFolder.getFile("config/config.json");
console.log(cfgFile.readTextSync());
}
}
As of TypeScript version 2.9.x and above (in NativeScript 5.x.x is using versions 3.1.1 and above) we can now use resovleJsonModule option for tsconfig.json. With this option, the JSON files can now be imported just as modules and the code is simpler to use, read and maintain.
For example, we can do:
import config from "./config.json";
console.log(config.count); // 42
console.log(config.env); // "debug"
All we need to do is to use TypeScript 2.9.x and above and enable the propety in tsconfig.json
// tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"resolveJsonModule": true,
"esModuleInterop": true
}
}
A sample project demonstrating the above can be found here
I just wanted to add one more thing, which might be even easier. You can simply write the content of your JSON file in a data.js file, or whatever name you would like to use, and export it as an array. Then you can just require the data.js module.