Casting JSON to complex types - json

I am trying to cast my http.get response to actual object -> in my specific case array of complex objects.
In a normal scenario, where you don't need any specific casting, you could do the following (simplified):
return this.httpClient.get(api, this._options_get)
.pipe(
map((response: any) => {
return response.value as NewProduct[];
})
);
As my need is to actually cast this to an object, I have created this static method which does that:
static toProduct(otherProduct: any): NewProduct {
let item = new NewProduct();
Object.keys(otherProduct).forEach(prop => {
if (typeof otherProduct[prop] === "object" && otherProduct[prop]) {
if (!item.hasOwnProperty(prop))
item[prop] = otherProduct[prop];
Object.assign(item[prop], otherProduct[prop]);
}
else
item[prop] = otherProduct[prop];
})
return item;
}
Under Object.assign I am taking already existing object which was initialized under first line and I am simply copying all the properties from the otherProduct to it. However I start to face problem when it comes to array of objects. Example (with simplified class):
export class Person {
name:string;
age:number;
addresses:Address[] = [];
}
export class Address {
street:string;
city:string;
fullAddress() : string { return this.street + this.city; }
}
As soon as I have this sort of array, I don't have any initial object in item. This means that there is no initial constructor of a class which results in simple Object. This is no error for JavaScript or TypeScript; however when I am trying to access internal method of a class (in our simplified case fullAddress(), I won't be able to.
The reason why I need that is that I am overriding toString() method on my sub-classes, which is necessary for MatTableDataSource when you use the filter method (which works with strings).
Is there a way how to retrieve elements from http.get() and properly map results to typed objects?

You're being too generic. You're creating objects of objects, not objects of Product with children of Addresses.
If you want to create a new product you're going to have to understand the relationship between the api's results and the data you want in the UI.
Because you're using classes and not interfaces and want to inherit the functions, the only way to get new Addresses into the new object is with the new keyword.
And you'll have to loop through. You're not going to find a shortcut for this. You're going to need to loop through the data and transform it. If your api is giving you an ApiPerson then you'll want to do something like this:
const addresses = apiPerson.addresses.map((apiAddress) => {
const address = new Address();
// map properties of apiAddress to address...
return address;
});
Now that you have the addresses, you can map the apiPerson to a new Person()'s properties and then set the newPerson.addresses = address.

Related

Kotlinx.Serialization using OkHTTPClient return always Failure

Hello I have a problem with my JSON. I am using OkHTTPClient to get JSON from web - to get objects from JSON using kotlinx.serialization via method which contains this and return value from method should be Result :
private suspend inline fun <reified T> OkHttpClient.get(webUrl: HttpUrl): Result<T> =
try {
//Builder defined here ... but skipping this line of code
val data = Json { ignoreUnknownKeys = true }.decodeFromString<T (result.body!!.string())
Result.Success(data)
} catch (e: Exception) {
Result.Failure(e)
}
suspend fun getFact(): Result<Fact> =
client.httpGet("myURL".toHttpUrl())
Json from myURL:
{"status":"success","data":[{"fact":"This is random information i need to get"}],"message":"Retrieved Fact"}
My serializer and Serializable data classes:
#Serializable
data class Fact(
#Serializable(with = FactListSerializer::class)
val data: String) java.io.Serializable
object FactListSerializer : JsonTransformingSerializer<List<String>>(ListSerializer(String.serializer())) {
override fun transformDeserialize(element: JsonElement): JsonElement {
return if (element is JsonArray) {
JsonArray(listOf(element)).first()
} else {
element
}
}
}
To be honest I am not sure what I am doing, but I am getting this error all the time when I print val fact = api.getFact():
Fact: Failure(error=kotlinx.serialization.json.internal.JsonDecodingException: Expected JsonPrimitive at 0, found {"fact":"This is random information i need to get"}
What I need to return is only first element of array fact, because JSON obtain always only 1 fact inside array. So I don't want to return from Serializer/Json List but only Fact object.
But as you see I am obtaining always Result Fauilure, don't know why. My goal is to obtain Result Success and obtaining from that JSON object Fact (only one), but I am not sure if I am doing it correct (obviously not) and even if it is even possible to return from JSONArray only one object (element of type Fact).
So what I expect is something like this:
Fact: Success(value=Fact(fact=This is random information i need to get))
I think the deserializer definition should be changed on 3 levels. The example of how to use JsonTransformingDeserializer in the docs actually describes most of what you need.
JsonArray(listOf(element)).first() should just be element.first(). Here you're building a JsonArray containing your initial JsonArray as only element, and then taking the first, so you basically get back the exact same element.
The type parameter T of JsonTransformingSerializer is supposed to be the type of the property it's applied to, so you should at least get a warning in the code because yours is defined to work on List<String> but is applied to a String property. It should be JsonTransformingSerializer<String>(String.serializer()).
You not only need to unwrap the data array, you also need to extract the value of the fact key within the element of that array.
So with all these changes, it should give something like this:
object FactListSerializer : JsonTransformingSerializer<String>(String.serializer()) {
override fun transformDeserialize(element: JsonElement): JsonElement {
val unwrappedData = if (element is JsonArray) element.first() else element
return unwrappedData.jsonObject["fact"] ?: error("missing 'fact' key in 'data' array")
}
}

How to convert JSON Array of Objects to Observable Array in angular

I am calling a rest API from angular, the rest api returns data in JSON Array of Objects like this
Now I am trying to convert it to my model class array but don't getting anywhere can you please guide me.
My Model Class
My Service File
Here it gives error on map and I don't know how to convert it to my model class array to display it in table
Resolved It.
As i was directly getting the array of objects in response, I don't need to convert it and use an interface. So here is my correct code
fetchAllGamesRecord() : Observable<Fifa[]>{
const fifaUrl = `${this.baseUrl}/fetchAllGamesRecord`;
return this.httpClient.get<Fifa[]>(fifaUrl);
}
This function is called like this
this.fifaService.fetchAllGamesRecord().subscribe(
data => {
this.allGameRecord = data;
console.log(`Data = `+data);
}
);
you can do that while calling fetchAllGamesRecord
fetchAllGamesRecord().subscribe( (response: Fifa[]) => {
// do something
}
where Fifa is interface not class
In the Spring framework also Fifa[] doesn't work in REST calls, we need to put it in another class as I showed below, I assume this will work.
export class FifaWrapper {
wrapper: Fifa[];
}
and also use this class in Observable
fetchAllGamesRecord(): Observable<FifaWrapper> {
... do you handling here
}

Typescript class with default values, how to parse JSON to this

I have a class of type A.
This class has several properties, let's call them prop1, prop2 and prop3.
When I'm calling an API, that returns a JSON string representing the object, some properties might be omitted if they are null. Further down the road, however, this object is used to construct a form dynamically (using Formik, but that's unrelated).
This framework expects all properties to be there, and some will be visible dynamically depending on other properties.
So my question, how can I parse a JSON response to my custom class, keeping default values in case properties are omitted in the API response?
What I've tried was:
static getCustomer(id) {
return fetch(process.env.MD_API_URL + 'customers/' + id, { mode: 'cors' })
.then(response => {
let cust = new Customer();
return response.json().then(x => cust = JSON.parse(x));
}).catch(error => {
return error;
});
}
But this returns undefined. Must be doing something wrong...
since typescript is not actually compiled but translated into javascript so all the javascript rules apply.
Therefore deserializing json wont actually create a new instance of the class in question but gives you an object you can "call" Customer during design time.
you could however create an object and then assign the json values like this:
export class Customer {
public id: number;
public name: string;
// your stuff here
public myDefaultProp: string = "default value";
public constructor(init?: Partial<Customer>) {
Object.assign(this, init);
}
}
your return then would look like this:
return response.json().then(x => new Customer(JSON.parse(x)));
added an example https://stackblitz.com/edit/typescript-16wlmg
This essentially just a matter of determining what to do in order to create an instance of a class, and map the properties of a JSON response towards your custom class, and there could be many different ways to solve this,
But I think (Factory function) is appropriate approach for this kind of task.

DataType for exposing data as json in mvc model

how can I introduce new property into my mvc model which will hold this cities json data.
public ActionResult Edit(int id)
{
MyModel model = repository.GetById(id);
if (model.CountryId.HasValue)
{
// load cities into new property
}
return this.View(result);
}
what data type should be my property (Cities) if I want to be exposed as json on client side for further js manipulation? ICollection? Is there any recommendation for this?
Set them up as any object type you like:
MyModel model = repository.GetById(id);
if (model.CountryId.HasValue)
{
model.Cities = repository.GetCities(model.CountryId.Value);
}
return this.View(model);
Then, on the client, you can use Json.NET library to serialize it. I use this trick when I'm trying to pass an array to a client-side JS component; essentially, it serializes the array of objects into a client-side array:
#(Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.Cities))
Whatever type of object represents each city is what determines what gets rendered out to the client; it simply reads the object and renders an equivalent JS object with all the same properties. You can fine tune that by saying:
Model.Cities.Select(i => new { i.ID, i.Name, .. })
Which only includes a subset. There are also attributes you can use to exclude certain columns I believe.

string linq to entities error - json

I am trying to map various markers on google map with a info window. It is all working fine till I try to pass a string through the controller.
I get the error below:
"LINQ to Entities does not recognize the method 'System.String GetMainPhoto(Int32)' method, and this method cannot be translated into a store expression."
I have read this is mainly caused because ToString method or some other methods are not valid to use. But, I am not really sure how to correct this error in this case.
Basically, I have a db - PropertyPhoto that holds the filename of the pictures. GetMainPhoto basically looks up all the rows and returns the main pic file name.
public string GetMainPhoto(int id)
{
return db.PropertyPhotos.Single(p => p.PropertyId == id && p.MainPic == true).PhotoLocation;
}
Controller is as follows:
public ActionResult Map()
{
if (Request.IsAjaxRequest())
{
var properties = websiteRepository.FindAllProperties();
var jsonProperties = from property in properties
select new JsonProperty
{
PropertyId = property.PropertyId,
NoOfBedroom = property.NoOfBedrooms,
Price = property.Price,
Address1 = property.PropertyAddress.Address1,
MainPicSrc = websiteRepository.GetMainPhoto(property.PropertyId),
Latitude = property.PropertyAddress.Latitude,
Longitude = property.PropertyAddress.Longitude
};
return Json(jsonProperties.ToList(), JsonRequestBehavior.AllowGet);
}
else
{
return View();
}
}
Try to eagerly load the data first by calling .ToList() before projecting into an anonymous type, otherwise EF doesn't know how to translate the websiteRepository.GetMainPhoto call to SQL:
var properties = websiteRepository.FindAllProperties().ToList();
Be careful though. By doing this you might be experiencing the SELECT N+1 problem because for each element of the initial resultset you will be sending a SQL query to fetch the MainPicSrc property.
A better approach would be to perform this join directly by the database and don't use the websiteRepository.GetMainPhoto call.