So, I am bound to use Lua for getting weather data from the Openweathermap API.
I managed to send a http request to return and store all data but now I am stuck with a Lua table I don't know how work with. I am very new to Lua and I didn't find any guide or similar regarding such a deep nested table in Lua.
In particular I am just interested in the field called temp in main. Here is a sample response from the API: Sample request response
The dependencies are Lua's socket.http and this json to Lua table formatter.
Here is my basic code structure
json = require ("json")
web = require ("socket.http")
local get = json.decode(web.request(<API Link>))
"get" now stores a table I don't know how to work with
With the help of https://www.json2yaml.com/, the structure is:
cod: '200'
message: 0.0036
cnt: 40
list:
- dt: 1485799200
main:
temp: 261.45
temp_min: 259.086
temp_max: 261.45
pressure: 1023.48
sea_level: 1045.39
grnd_level: 1023.48
humidity: 79
temp_kf: 2.37
weather:
- id: 800
main: Clear
description: clear sky
icon: 02n
clouds:
all: 8
wind:
speed: 4.77
deg: 232.505
snow: {}
sys:
pod: n
dt_txt: '2017-01-30 18:00:00'
…
- dt: 1486220400
…
city:
id: 524901
name: Moscow
coord:
lat: 55.7522
lon: 37.6156
country: none
So,
for index, entry in ipairs(get.list) do
print(index, entry.dt, entry.main.temp)
end
ipairs iterates over the positive integer keys in the table, up to but not including the first integer without a value. It seems that's the way the JSON library represent a JSON array.
If you don't know how to work with Lua tables you probably should learn the very basics of Lua. Refer to https://www.lua.org/start.html
The json string encodes a Lua table with all it's keys and values.
You can either read how the encoder encodes a table or you simply encode your own table and analyze the resulting json string.
print(json.encode({1,2,3}))
[1,2,3]
print(json.encode({a=1, b={1,2}, [3]="test"}))
{"3":"test","b":[1,2],"a":1}
and so on...
There's always table keys and values, separated by a colon.
Values can be numbers, strings, tables...
If the table only has numeric keys starting from one the value is a list of those values in brackets. If you have different keys in a table its encapsulated in curly brackets...
So let's have a look at your results. I'll remove 39 of the 40 entries to shorten it. I'll also indent to make the structure a bit more readable.
{
"cod":"200",
"message":0.0036,
"cnt":40,
"list":[{
"dt":1485799200,
"main":{
"temp":261.45,
"temp_min":259.086,
"temp_max":261.45,
"pressure":1023.48,
"sea_level":1045.39,
"grnd_level":1023.48,
"humidity":79,
"temp_kf":2.37},
"weather":[
{
"id":800,
"main":"Clear",
"description":"clear sky",
"icon":"02n"
}],
"clouds":{"all":8},
"wind":{"speed":4.77,"deg":232.505},
"snow":{},
"sys":{"pod":"n"},
"dt_txt":"2017-01-30 18:00:00"}
],
"city":{
"id":524901,
"name":"Moscow",
"coord":{
"lat":55.7522,
"lon":37.6156
},
"country":"none"
}
}
That sample response appears to have many subtables that have a main in them. Try this: get.list[1].main.temp.
After 2 days I finally found the error. I was working in a Minecraft Mod called OpenComputers which utilizes Lua. It seems like the mod uses a own version of socket.http and every time I wanted to print the response it returned two functions to use with request. I found out that if I put a "()" after the variable it returned the Response as a String and with the JSON library I could decode it into a workable table.
Side note: I could access the weather like this: json_table["weather"]["temp"]
The mod is pretty poorly documented on the http requests so I had to figure this out by myslef. Thanks for your responses, in the end the error was as always very unexpected!
Related
I'm new to Rust.
I try to write a websocket client.
This is my format message:
"[integer, "string", object]"
Is there away to store all value in Vector something like:
let msg: Vec<interface> = vec![123, "event", AnyObject{}];
How to convert it into string and vice versa.
Thanks in advance
Conceptually speaking you want to use an enum. The type of enums used in rust are called tagged unions. Essentially you can think of them as enums which can hold data.
enum Interface {
Int(i32),
String(&'static str),
Object(AnyObject),
// etc
}
// You can then create a vec of different enum variants
let msg: Vec<Interface> = vec![Interface::Int(123), Interface::String("event"), Interface::Object(AnyObject{})];
Assuming you are referring to JSON, then the recommended solution is to use serde with serde_json. serde_json provides a Value enum you can use to represent JSON data in an unknown layout.
// Copied from serde_json docs
enum Value {
Null,
Bool(bool),
Number(Number),
String(String),
Array(Vec<Value>),
Object(Map<String, Value>),
}
However, you don't need to use Value directly (unless you want to) since serde_json provides a macro to make this easier by letting you format your code like JSON.
use serde_json::{json, Value};
let msg: Value = json!([123, "event", {}]);
// You can then serialize a Value into JSON format
println!("{:?}", msg.to_string());
// Output: "[123,\"event\",{}]"
I recommend reading through the overview since they have a bunch of convenient ways for working with JSON.
So i'm just starting out with typescript and something that I can't find much info on is how to deal with types for api call responses.
Lets say, I make a GET request to an api and it returns a JSON object for example:
{
name: "john",
age: 12
}
in my code, if i wanted to interact with response.name would i have to create an interface like below to use response.name without having a red linter under it?
interface Response {
name: string,
age: number
}
or is there a easier way to do it as some api calls would return JSON > 10 lines long and copying out the whole structure for every type of call seems very troublesome. Another thought I had was to create the interface but only have values I would use instead of the whole structure, but i'm not too sure so any help would be greatly appreciated!
You can define Response as a "common" type, that supports all types of API json response.
interface IResponse {
[key: string]: any
}
Now, you can type response.name without the "red line", also response.not_exist_property is valid.
My recommendation is to define all types for all API response type:
interface GetUserResponse {
name: string,
age: number,
}
for GET /users/:id (example)
You can use this tool to convert a json response to Typescript type.
Background: Project uses Yelp API in JSON to sift through business data. Two types of endpoints are use - https://www.yelp.com/developers/documentation/v3/business_search and https://www.yelp.com/developers/documentation/v3/business. The first part of this projects works as expected where the business_search api is parsed, saved as a list of strings and converted to a Business objects when it is needed to be displayed. The JSON -> Object code is as follows:
factory Business.fromJSON(Map<String, dynamic> json) {
return Business(
rating: json['rating'],
price: json['price'],
phone: json['phone'],
id: json['id'],
name: json['name'],
alias: json['alias'],
isClosed: json['is_closed'],
reviewCount: json['review_count'],
url: json['url'],
imageUrl: json['image_url'],
);
}
The code works when a call is converted from the business/search endpoint, with the JSON printout being:
json = {rating: 4.5, price: $$, phone: +16316751500, id: D5dS2-8JXhZxo3BzMLV3xQ, name: Ichi sushi & ramen, alias: ichi-sushi-and-ramen, is_closed: false, review_count: 128, url: https://www.yelp.com/biz/ichi-sushi-and-ramen?adjust_creative=Aj73ii92JG9IDLNvRyn_Ow&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=Aj73ii92JG9IDLNvRyn_Ow, image_url: https://s3-media1.fl.yelpcdn.com/bphoto/Phib_AwTkdD_kBV8r7179Q/o.jpg}
The Issue: I encounter the following exception
[VERBOSE-2:ui_dart_state.cc(166)] Unhandled Exception: FormatException: Unexpected character (at character 2)
{id: D5dS2-8JXhZxo3BzMLV3xQ, alias: ichi-sushi-and-ramen...
^
when this response from business/details is called, saved and attempted to be converted to objects:
json = {id: D5dS2-8JXhZxo3BzMLV3xQ, alias: ichi-sushi-and-ramen, name: Ichi sushi & ramen, image_url: https://s3-media1.fl.yelpcdn.com/bphoto/Phib_AwTkdD_kBV8r7179Q/o.jpg, is_claimed: true, is_closed: false, url: https://www.yelp.com/biz/ichi-sushi-and-ramen?adjust_creative=Aj73ii92JG9IDLNvRyn_Ow&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_lookup&utm_source=Aj73ii92JG9IDLNvRyn_Ow, phone: +15836751500, display_phone: (583) 675-1500, review_count: 128, categories: [{alias: sushi, title: Sushi Bars}, {alias: ramen, title: Ramen}], rating: 4.5, location: {address1: 200 Country Rd, address2: null, address3: , city: Bayshore, zip_code: 11803, country: US, state: NY, display_address: [200 Country Rd, Bayshore, NY 11803], cross_streets: }, coordinates: {latitude: 40.92842, longitude: -73.116}, photos: [https://s3-media1.fl.yelpcdn.com/bphoto/Phib_AwTkdD_kBV8r7179Q/o.jpg, https://s3-media2.fl.yelpcdn.com/bp<…> (*My note: debugger truncates with <...>*)
What is going wrong here? I have been trying many (and I mean many) different cases to rule out what the issue is but cannot find anything that is blatantly amiss. The only difference I can see is that the first type of call (business/search) starts with "rating" and contains fewer fields while the second type of call (business/details) returns much more data (note that I do not need much of it and hence do not need it in the Business object, but the nature of Yelp's api requires I do it in this way) and starts with the "id" field instead. Are these differences to blame and what does this error mean? Can you "plug in" a JSON to an object that has more fields than needed properties as I would like to keep the Business object as versatile as possible? Regardless, how could this be resolved? If more info is needed, please let me know. Thank you for your help and time!
After further analyzing the code, #KhashayarMotarjemi's suggestion tipped me off on a possible problem: the way the data was encoded. To save the data, I ran the the JSON through the toString() method, stripping the data of all of its " " and making it unable to be correctly parsed as JSON later on when retrieved from memory. The correct method would be jsonEncode(yourData) as this converts it to a string to be saved, but preserves the format needed when decoded down the line. So to anyone reading, if you are trying to store your JSON as a string, you must use jsonEncode() and not toString() when doing so. I hope this can help someone in the future!
Apologies for this noob question.
I am trying to convert a string to json. The string is already in json format like
{ "system1": "Service 1", "System2": "Service2" }
or
{ "system1": "Service 1", "device": "Service 10", "Something": "port 22" }
and so on. The number of this key-value pair is not known at compile time, and is known only at the runtime.
I am able to load it to a struct, with predefined fixed keynames, but since the number of keys are varying, I am stuck at generating a json with respect to the structure of the string.
I am not looking for pushing it to a string : []map[string]string and my aim is to generate individually generate the key-value pair similar to python's json.loads on the string (Not prefering string : []map[string]string because to get an element out of it, I have to iterate over it, which takes O(n) time, but since the keys are known in runtime, and not a list, I could directly call it as if value.Key. Please correct me if I am wrong.)
The way I could do it with python was,
>>> a = { "system1": "Service 1", "System2": "Service2" }
>>> b = json.loads(a)
>>> b
{u'system1': u'Service 1', u'System2': u'Service2'}
and I could access elements as
b['system1']
without iterating over b, because it is not a slice.
Any guidance is appreciated.
Thanks,
Scott.
You can use unmarshal the string in a map[string]interface{}.
For example https://play.golang.org/p/fjgg0iQgV1
I want to parse a json string into an abap internal table, for example, this one
{
"apiVersion": "1.0",
"data": {
"location": "Dresden",
"temperature": "7",
"skytext": "Light rain",
"humidity": "96",
"wind": "7.31 km/h",
"date": "02-14-2017",
"day": "Tuesday"
}
}
I want to use the method cl_fdt_json=>json_to_data and put the values and keys into a table like this
types: begin of map,
key type string,
value type string,
end of map.
data json_data type standard table of map.
But, unfortunately, it does not work like that. Does anyone have experience with this kind of problem? I don't have access to all the documentation because this is my sample task for a hirement to SAP and this is the last part of the "puzzle" ;) It is hard for me to find the solution.
Thank you!!!
EDIT: accordingly to vwegerts answer I tried the following. This is a little bit different to what i originally wanted to do, but it would also be ok)
DATA cl_oops TYPE REF TO cx_dynamic_check.
DATA(text) = result.
TYPES: BEGIN OF ty_structure,
skytext TYPE string,
location type string,
temperature type string,
humidity type string,
wind type string,
date type string,
day type string,
END OF ty_structure.
DATA : wa_structure TYPE ty_structure.
TRY.
CALL TRANSFORMATION id
SOURCE XML text
RESULT data = wa_structure.
message wa_structure-skytext type 'I'.
CATCH cx_transformation_error INTO cl_oops.
WRITE cl_oops->get_longtext( ).
ENDTRY.
but it still doesnt work. when i check the value of wa_structure-skytext it is unfortunatly empty. i cannot find the mistake. does anyone have an idea?
Rather than use the FDT class (which might not be available on all systems), you might want to take a look at the well-documented capabilities of the ABAP runtime system itself. This example program might be the way to go for you. You would essentially provide a Simple Transformation that would map the JSON XML structure to your data structure, instantiate a sXML JSON reader and then pass that as source to CALL TRANSFORMATION.
Besides #vwegert recommendation to use the SAP documented json transformations, you could check the open source alternatives. This one looks promising.
{"apiVersion":"1.0", "data":{ "location":"Dresden", "temperature":"7", "skytext":"Light rain", "humidity":"96", "wind":"7.31 km/h", "date":"02-14-2017", "day":"Tuesday" } }
The corresponding structure in ABAP would be:
"The nested data table
Types: Begin of ty_data,
location TYPE string,
temperature TYPE string,
skytext TYPE string,
etc.
End of ty_data,
ty_t_data TYPE STANDARD TABLE OF ty_data WITH NON-UNIQUE DEFAULT KEY INITIAL SIZE 0.
"the whole json structure
Types: Begin of ty_json,
apiversion TYPE string,
data TYPE ty_t_data,
End of ty_json.
DATA: ls_data TYPE ty_json.
Now you have to find a proper JSON deserializer, which handles nested tables.
Most deserializer expect a table input, so you have to add '['... ']' at the end of your JSON string and define lt_data TYPE STANDARD TABLE OF ty_json.
You can do it like that via SAP JSON-XML reader:
CLASS lcl_json DEFINITION.
PUBLIC SECTION.
TYPES: BEGIN OF map,
key TYPE string,
value TYPE string,
END OF map,
tt_map TYPE STANDARD TABLE OF map WITH DEFAULT KEY.
CLASS-METHODS: parse IMPORTING iv_json TYPE string
RETURNING VALUE(rv_map) TYPE tt_map.
ENDCLASS.
CLASS lcl_json IMPLEMENTATION.
METHOD parse.
DATA(o_reader) = cl_sxml_string_reader=>create( cl_abap_codepage=>convert_to( iv_json ) ).
TRY.
DATA(o_node) = o_reader->read_next_node( ).
WHILE o_node IS BOUND.
CASE o_node->type.
WHEN if_sxml_node=>co_nt_element_open.
DATA(op) = CAST if_sxml_open_element( o_node ).
LOOP AT op->get_attributes( ) ASSIGNING FIELD-SYMBOL(<a>).
APPEND VALUE #( key = <a>->get_value( ) ) TO rv_map ASSIGNING FIELD-SYMBOL(<json>).
ENDLOOP.
WHEN if_sxml_node=>co_nt_value.
DATA(val) = CAST if_sxml_value_node( o_node ).
<json>-value = val->get_value( ).
WHEN OTHERS.
ENDCASE.
o_node = o_reader->read_next_node( ).
ENDWHILE.
CATCH cx_root INTO DATA(e_txt).
RAISE EXCEPTION TYPE cx_sxml_parse_error EXPORTING error_text = e_txt->get_text( ).
ENDTRY.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
DATA(json_string) = ` {"apiVersion":"1.0", ` &&
` "data":{ "location":"Dresden", "temperature":"7",` &&
` "skytext":"Light rain", "humidity":"96", "wind":"7.31 km/h", "date":"02-14-2017", "day":"Tuesday" } } `.
TRY.
DATA(it_map) = lcl_json=>parse( json_string ).
CATCH cx_root INTO DATA(e_txt).
" do handling
ENDTRY.