I have some problem parsing javaScript objects received from a web server.
These objects supposed to be JSON but contains numeric keys.
javaScript object:
{a: "alpha", 2: "two"}
The question is how can i decode such javascript objects in java ?
Are there any existing libraries that could be used ? And if there are how to represent that object in java :
public class JavaObject {
private String a; // for field - a: "alpha"
private ??? // for field - 2: "two"
}
Thanks
As i didn't find any solution for my problem on the web, i implemented myself an adapter for GSON library to handle such data.
As well known JSON supports only strings keys.
If a have a json data like this {"a": "alpha", "2": "two", "3":3} i can convert it in a regular JSONObject, but still i can't convert it it my own java object.
To handle this situation i created a ObjectMap object
// ObjectMap.java
import java.util.Map;
public class ObjectMap<V> {
private Map<String, V> map;
public ObjectMap(Map<String, V> map) {
setMap(map);
}
public Map<String, V> getMap() {
return map;
}
public void setMap(Map<String, V> map) {
this.map = map;
}
#Override
public String toString() {
return "ObjectMap [map=" + map + "]";
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((map == null) ? 0 : map.hashCode());
return result;
}
#SuppressWarnings("rawtypes")
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ObjectMap other = (ObjectMap) obj;
if (map == null) {
if (other.map != null)
return false;
} else if (!map.equals(other.map))
return false;
return true;
}
}
and a gson adapter for it ObjectMapAdapter.
//ObjectMapAdapter.java
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
public class ObjectMapAdapter<V> extends TypeAdapter<ObjectMap<V>> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
#SuppressWarnings({ "unchecked", "rawtypes" })
public <T> TypeAdapter create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
Class<? super T> rawType = typeToken.getRawType();
if (!ObjectMap.class.isAssignableFrom(rawType)) {
return null;
}
// if (rawType != ObjectMap.class) {
// return null;
// }
Type componentType;
if (type instanceof ParameterizedType) {
componentType = ((ParameterizedType) type).getActualTypeArguments()[0];
} else {
componentType = Object.class;
}
TypeAdapter<?> componentTypeAdapter = gson.getAdapter(TypeToken.get(componentType));
return new ObjectMapAdapter(gson, componentTypeAdapter, $Gson$Types.getRawType(componentType));
}
};
// private final Class<V> valueType;
private final TypeAdapter<V> valueTypeAdapter;
public ObjectMapAdapter(Gson context, TypeAdapter<V> componentTypeAdapter, Class<V> componentType) {
this.valueTypeAdapter = new TypeAdapterRuntimeTypeWrapper<V>(context, componentTypeAdapter, componentType);
// this.valueType = componentType;
}
public ObjectMap<V> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
Map<String, V> map = new LinkedHashMap<String, V>();
in.beginObject();
while (in.hasNext()) {
String key = in.nextName();
V comp = valueTypeAdapter.read(in);
map.put(key, comp);
}
in.endObject();
return new ObjectMap<V>(map);
}
#Override
public void write(JsonWriter out, ObjectMap<V> map) throws IOException {
if (map == null) {
out.nullValue();
return;
}
out.beginObject();
for (Entry<String, V> entry : map.getMap().entrySet()) {
out.name(entry.getKey());
valueTypeAdapter.write(out, entry.getValue());
}
out.endObject();
}
}
Create a custom GSON builder and register this factory
gsonBuilder.registerTypeAdapterFactory(ObjectMapAdapter.FACTORY);
the you are ready to decode data like this
{"a": "alpha", "2": "two", "3":3}
into a map
ObjectMap [map={a=alpha, 2=two, 3=3}]
Related
I need to create a dictionary object in typescript, something that has the following function:
obj.add(key, value);
obj.remove(key);
obj.contains(key); // returns true/false
obj.get(key); // returns value
This was the model I created to accommodate:
export class Dictionary<T, F> {
items = {};
constructor() {
this.items = {};
}
public contains(key: T): boolean {
return key in this.items;
}
public add(key: T, value: F): void {
this.items[key] = value;
}
public get(key: T): F {
return this.items[key];
}
public remove(key: T): boolean {
if (this.contains(key) ){
delete this.items[key];
return true;
}
return false;
}
}
However, I keep getting this error:
Error: src/app/models/dictionary.model.ts:10:7 - error TS2536: Type 'T' cannot be used to index type '{}'.
10 this.items[key] = value;
Has anyone come across this before and can recommend the most appropriate fix?
Thanks in advance for any pointers (examples welcome :-))!
This used to be a 415 error question.
Now it is a a receiving null values on the server side question.
I am having difficulty trying to get my values in the object myMessage over to the server side.
I have so far tried to add JSON.stringify to newMessage which is being console.logged in the service file.
I tried many ways to alter or make the object the way it would be recognized such as JSON.stringify() and creating a url ending with the correct parameters.
Sorry if it seems like I am dumping code below, but I have been working on this for a second day and don't understand why I can't do a simple post request with three parameters. One string, one int, and one datetime.
If anyone can see where I have gone wrong I would so appreciate it. I will be desperately waiting.
Below I am trying to hit api/SlgCorpNotes/Edit in backend from updateMessage(message: any) in the service in service.ts
slg-corp-notes.service.ts
import { Component, Injectable, Inject } from '#angular/core';
import { HttpClient, HttpHeaders, HttpResponse } from '#angular/common/http';
import { Observable, Subject, BehaviorSubject } from 'rxjs';
import { SLGReportParams, CorpNotes } from './models/slg.model';
import { SlgOverviewComponent } from './slg-overview/slg-overview.component';
import { SlgNote } from './models/slg-notes';
#Injectable({
providedIn: 'root'
})
export class SlgCorpNotesService {
constructor(private http: HttpClient, #Inject('BASE_URL') private baseUrl: string) { }
getWeekTempValue(endDate, department) {
var Params = '?endDate=' + endDate + '&department=' + department;
return this.http.get<any>(this.baseUrl + 'api/SlgCorpNotes/getWeekTempValue' + Params);
}
updateMessage(message: any) {
console.log("at service")
console.log(message)
var newMessage = new CorpNotes(message['departments'], message['noteBody'], message['weeks'].weekEnding)
var Params = '?Department=' + message['departments'] + '&Note=' + message['noteBody'] + '&WeekEnding=' + message['weeks'].weekEnding
console.log(newMessage)
console.log(JSON.stringify(newMessage))
console.log(Params)
const headers = new HttpHeaders()
.set('Content-Type', 'application/json;charset=UTF-8')
let options = { headers: headers };
return this.http.post(this.baseUrl + 'api/SlgCorpNotes/Edit', JSON.stringify(newMessage), options).subscribe(res => {
console.log(res);
}, error => {
console.log(error);
});;
}
}
model.ts
export class CorpNotes {
constructor(
public department: number,
public note: string,
public weekEnding: Date
) { }
}
SLGCorpNotesController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using mocHub2.Models;
using mocHub2.Models.Enterprise;
using Microsoft.EntityFrameworkCore;
using System.Data.SqlClient;
namespace mocHub2.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class SlgCorpNotesController : Controller
{
SLGContext _SLGContext;
BRDataContext _BRDataContext;
//injects new context
public SlgCorpNotesController(SLGContext context, BRDataContext context2)
{
_SLGContext = context;
_BRDataContext = context2;
}
// GET: api/SlgCorpNotes
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET: api/SlgCorpNotes/5
[HttpGet("{id}", Name = "Get")]
public string Get(int id)
{
return "value";
}
// POST: api/SlgCorpNotes
[HttpPost]
public void Post([FromBody] string value)
{
}
// Get Corporate Notes
[HttpGet("[action]")]
public JsonResult getWeekTempValue(DateTime endDate, int department)
{
// Find the WeekID from the weekending from SLGHeaderTemplate table
var WeekID = (from x in _SLGContext.SlgheaderTemplate
where x.WeekEnding == endDate
select x.Id).ToList();
// Find Department name by ID
var DepartmentString = (from x in _BRDataContext.Departments
where x.Department == department
select x.Description).ToList();
// Get the Note.
var DeptNote = from x in _SLGContext.SLGCorpNotes
where x.Department == DepartmentString[0]
&& x.WeekID == WeekID[0]
select x.Notes;
// Create return object
var notes = new Notes();
// If Note exists then return Json containing note and department for display, else return empty string.
if (DeptNote.Any() && WeekID.Count() > 0 && DepartmentString.Count() > 0)
{
var ReturnDeptNote = DeptNote.First();
notes = new Notes() { WeekID = WeekID[0], Department = DepartmentString[0], Note = ReturnDeptNote };
}
else
{
var ReturnDeptNote = "";
notes = new Notes() { WeekID = WeekID[0], Department = DepartmentString[0], Note = ReturnDeptNote };
}
return Json(notes);
}
[HttpPost]
[Route("Edit")]
public void Edit([FromForm] CorpNotes item)
{
_SLGContext.Entry(item).State = EntityState.Modified;
_SLGContext.SaveChanges();
}
}
public class CorpNotes
{
public int department { get; set; }
public string note { get; set; }
public DateTime weekEnding { get; set; }
}
public class Notes
{
public int ID { get; set; }
public int WeekID { get; set; }
public string Department { get; set; }
public string Note { get; set; }
}
}
Results of console.logs in the service file.
at service
slg-corp-notes.service.ts:22 {departments: 2, weeks: SLGTime, noteBody: "asdf"}
slg-corp-notes.service.ts:25 CorpNotes {department: 2, note: "asdf", weekEnding: "2019-11-02T00:00:00"}
slg-corp-notes.service.ts:26 {"department":2,"note":"asdf","weekEnding":"2019-11-02T00:00:00"}
slg-corp-notes.service.ts:27 ?Department=2&Note=asdf&WeekEnding=2019-11-02T00:00:00
slg-corp-notes.service.ts:28 Observable {_isScalar: false, source: Observable, operator: MapOperator}
app.module.ts
This is in my app.module.ts where I specify routes
{ path: 'slg-corp-notes', component: SlgCorpNotesComponent },
{ path: 'slg-corp-notes/edit/', component: SlgCorpNotesComponent }
slg-corp-notes.component.ts
save() {
console.log("at save")
if (!this.optionsForm.valid) {
return;
}
//this.Notes.note = this.optionsForm.get['noteBody'].value;
console.log(this.Notes);
this._slgCorpNotesService.updateMessage(this.optionsForm.value)
.subscribe((data) => {
this._router.navigate(['/slg-corp-notes']); //This will navigate back to the mochhub2 index where the message will be displayed
}, error => this.errorMessage = error)
}
Please let me know if additional info is needed.
1) You need to set the Content-Type header to application/json.
2) stringify the message.
const headers = new HttpHeaders()
.set('Content-Type', 'application/json;charset=UTF-8')
let options = { headers : headers };
this.http.post(this.baseUrl + 'api/SlgCorpNotes/Edit', JSON.stringify(newMessage), options);
At your angular side update your method like this
updateMessage(message: any) {
console.log("at service")
console.log(message)
var newMessage = new CorpNotes(message['departments'], message['noteBody'], message['weeks'].weekEnding)
var Params = '?Department=' + message['departments'] + '&Note=' + message['noteBody'] + '&WeekEnding=' + message['weeks'].weekEnding
console.log(newMessage)
console.log(JSON.stringify(newMessage))
console.log(Params)
var item = {
"Departments": message["Departments"],
"Note": message["noteBody"],
"WeekEnding": message["weeks"]
}
return this.http.post(this.baseUrl + 'api/SlgCorpNotes/Edit', item).subscribe(res
=> {
console.log(res);
}, error => {
console.log(error);
});
}
I am having a bit of trouble working with maps int Typescript. What I am trying to do is to use a HashMap smilier to that found in Java for example here is my Java object,
public class Payment {
private String id;
private DateTime created;
//when they actually received the payment
private DateTime paidDate;
private String customerId;
private String companyId;
private BigDecimal amount;
private BigDecimal allocated;
private String description;
// it must be the Id of PaymentMethod
private String type;
//to improve it
private String currency;
private Boolean fullPaid = false;
private Boolean lockArchive = false;
//<InvoiceId, Amount>
private HashMap<String, BigDecimal> invoices = new HashMap<>();
//getter and setters....
So what I have done in Typescript it to create a similar class for example,
export class Payment {
public id?:string;
public created?:string;
public paid_date?:Date;
public customer_id?:string;
public company_id?:string;
public amount?:number;
public allocated?:number;
public description?:string;
public type?:string;
public currency?:string;
public full_paid?:boolean;
public lock_archive?:boolean;
public invoices: {[invoice_id:string]:number};
constructor(id: string, created: string, paid_date: Date, customer_id: string, company_id: string, amount: number, allocated: number, description: string, type: string, currency: string, full_paid: boolean, lock_archive: boolean, invoices: { [p: string]: number }) {
this.id = id;
this.created = created;
this.paid_date = paid_date;
this.customer_id = customer_id;
this.company_id = company_id;
this.amount = amount;
this.allocated = allocated;
this.description = description;
this.type = type;
this.currency = currency;
this.full_paid = full_paid;
this.lock_archive = lock_archive;
this.invoices = invoices;
}
}
I am trying to to basically add to the the invoices map so I have the following function,
public invoicesMap = new Map<string, number>();
constructor(public dialogRef: MdDialogRef<PaymentInvoiceSelectDialogComponent>,
private customerService:CustomerService,
private paymentServices: PaymentsService) {
}
ngOnInit() {
this.customer.subscribe((res)=>{
this.customer_name = res.customer_name
}, error=>{
console.error(<any>error);
});
this.customerService.getListOfCustomerInvoices(this.customerId,'0','25')
.subscribe( (res) =>{
this.invoices = res;
},
error => {
console.error(<any>error);
});
}
selectedInvoice(invoiceId: string, amount: number, event:any) {
if(event.checked){
if(!_.isEmpty(this.payment.invoices)){
for(let [key, value] of this.invoicesMap) {
if (key !== invoiceId) {
this.invoicesMap.set(invoiceId, amount);
for(let [key, vvalue] of this.invoicesMap) {
if (key === invoiceId) {
this.availableAllocation = this.availableAllocation - vvalue;
}
}
}
}
} else {
this.invoicesMap.set(invoiceId,amount);
for(let [key, vvalue] of this.invoicesMap) {
if(key === invoiceId){
this.availableAllocation = this.amount - vvalue;
}
}
}
} else if(!event.checked){
for(let [key, vvalue] of this.invoicesMap) {
if (key === invoiceId) {
console.log("removing an item");
this.availableAllocation += vvalue;
}
}
}
//at this point I would like to set this.payment.invoices to this.invoiceMap
for(let [key, value] of this.invoicesMap){
let v = {invoice_id:key,amount:value};
console.log(v);
}
this.payment.allocated = this.availableAllocation;
}
savePayment(){
console.log(JSON.stringify(this.payment));
// this.paymentServices.updatePayment(this.payment)
// .subscribe((res)=>{
// this.payment = res;
// this.dialogRef.close(this.payment);
// },
// error =>{
// console.log(<any>error);
// });
}
the items are added to the invoiceMap but the problem I am having now is adding invoiceMap to payment.invoices. If someone can point me in the right direction that would be much appreciated. Thanks
You can change it to a Map in Payment as well:
public invoices: Map<string, number>;
And then you can simply assign the map that you have.
Or you can iterate over the map entries and turn them into an object like you have in Payment:
let m = new Map<string, number>();
let m2 = {} as { [key: string]: number };
m.forEach((value, key) => m2[key] = value);
If you are gonna use String as keys there is no reason to use a hashmap, every object in javascript, as well as typescript, is a simple map. The only reason you would need to use the Map class is if you need something other than a String as a key.
Please take a look at this question -> How is a JavaScript hash map implemented?
if I understand what do you want you can use TypeLite : http://type.litesolutions.net/
this addon convert any class c# in typescript class.
before the async comunication you parse the request to json and into your code behind( or into controller )you response with your object serialized into json.
The most simple way is to use Record type Record<string, any>
const invoicesMap : Record<string, any>
This would allow you to have a type with any any string and values. Only use it if you don't know the keys in advance.
You can also look at Partial type. If you have some values but not all.
https://www.typescriptlang.org/docs/handbook/utility-types.html
Spring Controller Method :
#RequestMapping(value="/checklist/{id}",method=RequestMethod.PUT, consumes=MediaType.APPLICATION_JSON_VALUE , produces=MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Checklist update(#RequestBody Checklist checklist, #PathVariable("id") int id)
{
checklist.setId(id);
return service.update(checklist);
}
JavaScript AJAX code:
var checklist={name:$('#newName').val(), details:$('#newDetails').val()};
$.ajax({ //send updated item values to
method:'put',
url:'/tracker/checklist/'+$(editingItem).attr('id'),
contentType:'application/json',
dataType:'json',
data:checklist,
success:function(data)
{
console.log(data);
$('#myModal').modal('hide');
}
});
Checklist Model:
package com.tracker.web.models;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
#Entity
#Table(name="checklists")
public class Checklist {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private int item_order;
private String name;
private String details;
private String phase;
private String completed;
private String skipped_note;
private Date completed_on;
private int completed_by;
#Temporal(TemporalType.TIMESTAMP)
#CreationTimestamp
private Date created_at;
#Temporal(TemporalType.TIMESTAMP)
#UpdateTimestamp
private Date updated_at;
#ManyToOne
private Event event;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getItem_order() {
return item_order;
}
public void setItem_order(int item_order) {
this.item_order = item_order;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
public String getPhase() {
return phase;
}
public void setPhase(String phase) {
this.phase = phase;
}
public String getCompleted() {
return completed;
}
public void setCompleted(String completed) {
this.completed = completed;
}
public String getSkipped_note() {
return skipped_note;
}
public void setSkipped_note(String skipped_note) {
this.skipped_note = skipped_note;
}
public Date getCompleted_on() {
return completed_on;
}
public void setCompleted_on(Date completed_on) {
this.completed_on = completed_on;
}
public int getCompleted_by() {
return completed_by;
}
public void setCompleted_by(int completed_by) {
this.completed_by = completed_by;
}
public Date getCreated_at() {
return created_at;
}
public void setCreated_at() {
this.created_at = new Date();
}
public Date getUpdated_at() {
return updated_at;
}
public void setUpdated_at() {
this.updated_at = new Date();
}
public Event getEvent() {
return event;
}
public void setEvent(Event event) {
this.event = event;
}
}
I am using Jquery 1.11. when i use with 'GET' instead of 'PUT' 'method' on client side and 'consumes' on server side, it works. even i tried with JSON.stringify while sending sending. i am using jackson on server side to convert data into json
Which version of jquery you are using?
If you are using jquery prior to 1.9.0 than try type: 'PUT' instead of method:'put' in your ajax call and it should work. Otherwise it should be of with method:'put' .
Check documentation for more reference http://api.jquery.com/jquery.ajax/
How do I serialize these classes to JSON?
As you can see from the example below JSON.stringify() does not serialize the list of Cache_Backend_LocalStorage_Tag inside the Cache_Backend_LocalStorage_TagThree object.
What am I missing?
interface Cache_Backend_LocalStorage_Tag_Interface {
_tag : string;
_keys : string[];
}
class Cache_Backend_LocalStorage_Tag implements Cache_Backend_LocalStorage_Tag_Interface {
public _tag : string;
public _keys : string[];
constructor(tag : string) {
this._tag = tag;
this._keys = [];
}
public add(key : string) : void {
this._keys.push(key);
}
public remove(key : string): boolean {
// Get the index of the key
var index = this._keys.indexOf(key, 0);
// Check if we found the keys index
if (index != undefined) {
this._keys.splice(index, 1);
return true;
}
return false;
}
public get tag(): string {
return this._tag;
}
public get keys(): string[] {
return this._keys;
}
}
interface Cache_Backend_LocalStorage_TagThree_Interface {
_tags : Cache_Backend_LocalStorage_Tag[];
}
class Cache_Backend_LocalStorage_TagThree implements Cache_Backend_LocalStorage_TagThree_Interface {
public _tags : Cache_Backend_LocalStorage_Tag[];
constructor(tags : Cache_Backend_LocalStorage_Tag[] = []) {
this._tags = tags;
}
public add(tag : Cache_Backend_LocalStorage_Tag) : void {
this.tags[tag.tag] = tag;
}
public get tags(): Cache_Backend_LocalStorage_Tag[] {
return this._tags;
}
public get(tagKey : string): Cache_Backend_LocalStorage_Tag {
return this.tags[tagKey];
}
public addKeyToTag(tagKey, key) {
this.tags[tagKey].add(key);
}
public removeKeyFromTag(tagKey, key) {
// Get the tag
var tag = this._tags[tagKey];
// Check if we found the tag index
if (tag != undefined) {
return tag.remove(key);
}
return false;
}
public clear(tagKey : string): void {
delete this._tags[tagKey];
}
public static fromObject(object): Cache_Backend_LocalStorage_TagThree {
return new Cache_Backend_LocalStorage_TagThree(object._tags);
}
}
Issue:
var tagThree = new Cache_Backend_LocalStorage_TagThree();
tagThree.add(new Cache_Backend_LocalStorage_Tag("stores"));
tagThree.addKeyToTag("stores", "store5");
tagThree.removeKeyFromTag("stores", "store5");
// {"_tags":[]}
console.log(JSON.stringify(tagThree));
// { _tags: [ stores: { _tag: 'stores', _keys: [Object] } ] }
console.log(tagThree);
Reason
It's because you're assigning properties to an array and array properties won't be included in JSON serialization. For example:
var a = [];
a["test"] = "some value";
JSON.stringify(a); // returns: []
You need to use a plain object:
var o = {};
o["test"] = "some value";
JSON.stringify(o); // returns: {"test":"some value"}
Solution
Change your Cache_Backend_LocalStorage_TagThree_Interface interface to use a dictionary like object:
interface Cache_Backend_LocalStorage_TagThree_Interface {
_tags : { [tag: string] : Cache_Backend_LocalStorage_Tag; };
}
Then update all areas of the code that will now have a compile error to use the same type. For example, change:
constructor(tags : Cache_Backend_LocalStorage_Tag[] = []) {
To:
constructor(tags : { [tag: string] : Cache_Backend_LocalStorage_Tag; } = {}) {
Just for fun - Changing default serialization behaviour (Not recommended)
If you really want to make this work with an array with your current setup (I'm not sure why), you can override how serialization is done. To do this, add a toJSON method to the _tags array in Cache_Backend_LocalStorage_TagThree. This allows you to control how the object is serialized when JSON.stringify is called on it. For example:
this._tags.toJSON = function() {
var values = [];
for (var v in this) {
if (this[v] instanceof Cache_Backend_LocalStorage_Tag) {
values.push(this[v]);
}
}
return JSON.stringify(values);
};