supposing I have to read some data from some json files(i18n), every json file may look like:
{
"foo": "1",
"bar": "2",
...
}
I don't know how many fields this json have(it can be expanded), but it's fields look like
{
[prop: string]: string
}
besides, all the json files share the same fields.
when I try to read a value from this json via:
//a can be expanded, I'm not sure how many fileds does it have
let a = {
name: "dd",
addr: "ee",
}
//I'm confident a has a field "name"
let b = "name";
console.log(a[b]);
the error message is:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type
how could I fix it?
The error you're encountering is because the keys in a is not just any string (in fact, it can only be "name" or "add"), but b can be a string of any arbitrary value. If you are very sure that b represents a key found in the object a, you can hint TypeScript as such:
let b: keyof typeof a = "name";
Attempting to assign any arbitrary string value to b will lead to an error:
// This will cause an error
let b: key typeof a = "foobar";
See proof-of-concept on TypeScript Playground.
I have two types of JSON: result and error.
I'm trying to deserialize these two JSON to my internal structure, but there's a problem.
Only for result the function works correctly, for error the structure is always blank.
Can anybody help me to solve my problem or indicate my mistake?
Here are my JSON:
{
"result": [
{
"to": "to_somebody",
"id": "some_id",
"code": "some_code"
}
]
}
{
"error": {
"name": "some_name",
"date": [],
"id": "11",
"descr": "Unknown error"
},
"result": null
}
Here is my ABAP code (there's a screen to enter the JSON):
DATA go_textedit TYPE REF TO cl_gui_textedit.
PARAMETERS: json TYPE string.
AT SELECTION-SCREEN OUTPUT.
IF go_textedit IS NOT BOUND.
CREATE OBJECT go_textedit
EXPORTING
parent = cl_gui_container=>screen0.
go_textedit->set_textstream( json ).
ENDIF.
AT SELECTION-SCREEN.
go_textedit->get_textstream( IMPORTING text = json ).
cl_gui_cfw=>flush( ).
TYPES: BEGIN OF stt_result,
to TYPE string,
id TYPE string,
code TYPE string,
END OF stt_result.
TYPES: BEGIN OF stt_error,
name TYPE string,
date TYPE string,
id TYPE string,
descr TYPE string,
result TYPE string,
END OF stt_error.
DATA: BEGIN OF ls_response_result,
result TYPE STANDARD TABLE OF stt_result,
error TYPE STANDARD TABLE OF stt_error,
END OF ls_response_result.
/ui2/cl_json=>deserialize( EXPORTING json = lv_cdata
pretty_name = /ui2/cl_json=>pretty_mode-camel_case
CHANGING data = ls_response_result ).
Revise your type declarations. There is a discrepancy in one place where you expect result as a JSON array (ABAP table) vs. a JSON object (ABAP structure).
This is the complete code I used:
CLASS the_json_parser DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES:
BEGIN OF result_structure,
to TYPE string,
id TYPE string,
code TYPE string,
END OF result_structure.
TYPES result_table TYPE
STANDARD TABLE OF result_structure
WITH EMPTY KEY.
TYPES date_table TYPE
STANDARD TABLE OF string
WITH EMPTY KEY.
TYPES:
BEGIN OF error_structure,
name TYPE string,
date TYPE date_table,
id TYPE string,
descr TYPE string,
result TYPE string,
END OF error_structure.
TYPES:
BEGIN OF complete_result_structure,
result TYPE result_table,
error TYPE error_structure,
END OF complete_result_structure.
CLASS-METHODS parse
IMPORTING
json TYPE string
RETURNING
VALUE(result) TYPE complete_result_structure.
ENDCLASS.
CLASS the_json_parser IMPLEMENTATION.
METHOD parse.
/ui2/cl_json=>deserialize(
EXPORTING
json = json
pretty_name = /ui2/cl_json=>pretty_mode-camel_case
CHANGING
data = result ).
ENDMETHOD.
ENDCLASS.
Verified with the test class:
CLASS unit_tests DEFINITION FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.
PUBLIC SECTION.
METHODS parses_result FOR TESTING.
METHODS parses_error FOR TESTING.
ENDCLASS.
CLASS unit_tests IMPLEMENTATION.
METHOD parses_result.
DATA(json) = `{` &&
`"result": [` &&
`{` &&
`"to": "to_somebody",` &&
`"id": "some_id",` &&
`"code": "some_code"` &&
`}` &&
`]` &&
`}`.
DATA(result) = the_json_parser=>parse( json ).
cl_abap_unit_assert=>assert_not_initial( result ).
cl_abap_unit_assert=>assert_not_initial( result-result ).
cl_abap_unit_assert=>assert_initial( result-error ).
ENDMETHOD.
METHOD parses_error.
DATA(json) = `{` &&
`"error": {` &&
`"name": "some_name",` &&
`"date": [],` &&
`"id": "11",` &&
`"descr": "Unknown error"` &&
`},` &&
`"result": null` &&
`}`.
DATA(result) = the_json_parser=>parse( json ).
cl_abap_unit_assert=>assert_not_initial( result ).
cl_abap_unit_assert=>assert_initial( result-result ).
cl_abap_unit_assert=>assert_not_initial( result-error ).
ENDMETHOD.
ENDCLASS.
A JSON object {...} can be mapped only by an ABAP structure.
A JSON array [...] can be mapped only by an ABAP internal table.
In your code, the error JSON is a JSON object, but the ABAP variable is an internal table.
So you should correct the ABAP variable by removing STANDARD TABLE OF so that it becomes a structure:
DATA: BEGIN OF ls_response_result,
result TYPE STANDARD TABLE OF stt_result,
error TYPE stt_error, " <=== "STANDARD TABLE OF" removed
END OF ls_response_result.
Thanks to all for your advice.
I solve my problem.
The case was:
I get from provider two types of JSON: result or error.
I can't get both of them at the same time.
I need to deserialize them to my internal structure.
{
"result": [
{
"to": "some_value",
"id": "some_id",
"code": "some_code"
}
]
}
and
{
"error": {
"name": "some_name",
"date": [some_date],
"id": "some_id",
"descr": "some_description"
},
"result": null
}
Here's my needed ABAP code, which works correctly.
P.S: My mistake was that I worked with error like with an internal table instead of structure.
TYPES: BEGIN OF stt_result,
to TYPE string,
id TYPE string,
code TYPE string,
END OF stt_result.
TYPES lt_data TYPE STANDARD TABLE OF string WITH EMPTY KEY.
TYPES: BEGIN OF stt_error,
name TYPE string,
date TYPE lt_data,
id TYPE string,
descr TYPE string,
result TYPE stt_result,
END OF stt_error.
DATA: BEGIN OF ls_response_result,
result TYPE STANDARD TABLE OF stt_result,
error TYPE stt_error,
END OF ls_response_result.
/ui2/cl_json=>deserialize( EXPORTING json = lv_cdata
pretty_name = /ui2/cl_json=>pretty_mode-camel_case
CHANGING data = ls_response_result ).
I was interested to learn if there is a way to have this:
type Example struct {
Name string `json:"name"`
Value string `json:"value"`
}
become this:
type Example struct {
Name string
Value string
}
and still allow for JSON marhaling/unmarshaling? Is there a library or a way to automatically assume or inject these tags without having to be explicit in every struct field?
By default it will be encoded as
{
"Name": "",
"Value": ""
}
But go is writen on go and if you really really want lowercase without tags, you can just 'fork' encoding/json package, recreate it as your own package and change behavior in encode.go (line 1151 in go 1.13):
// Record found field and index sequence.
if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
tagged := name != ""
if name == "" {
name = sf.Name <-- here, for example to strings.ToLower(sf.Name)
}
....
I not recommend you to do that, but it works :)
I do have this struct for my request
type RequestBody struct {
ForkliftID string `json:"forklift_id"`
WarehouseID string `json:"warehouse_id,omitempty"`
TaskID string `json:"task_id"`
}
If I don't send the "forklift_id" in the json request body, the unmarshalling assigns "" without returning error, I wonder if there is something like
`json: "forklift_id,notempty"`
so the unmarshalling returns an error if the field is not present or empty.
Thanks in advance
I assume what you did (treating empty value from payload as an error) is for validation purposes. If so, I think you can go with #colminator answer, or try to use 3rd party library that designed to solve this particular problem. One example library is https://github.com/go-playground/validator.
type RequestBody struct {
ForkliftID string `json:"forklift_id" validate:"required"`
WarehouseID string `json:"warehouse_id" validate:"required"`
TaskID string `json:"task_id"`
}
// ...
var payload RequestBody
// ...
validate := validator.New()
err := validate.Struct(payload)
if err != nil {
// handle the error
}
The field with tag validate:"required" will be validated when validate.Struct() called.
There are also a lot of useful validation rules available other than required.
For a more detailed example, take a look at the example source code
Another alternative solution would be, by performing explicit checks across those struct's fields. Example:
// ...
if payload.ForkliftID == "" {
err = fmt.Errorf("Forklift ID cannot be empty")
}
if payload.WarehouseID == "" {
err = fmt.Errorf("Warehouse ID cannot be empty")
}
Change the field type to *String. After unmarshaling, if the value is nil, then you know the JSON field was not provided.
See here for a more thorough example of unmarshaling error-checking.
I want to write a function with a parameter type guard that accepts the value from a K/V pair from an object or type...
type VodTreeName = {
Movie: 'movie_vod',
TV: 'tv_vod',
VideoStore: 'video_store'
};
function test(something: VodTreeName) {
// expecting something === 'movie_vod'
}
test(VodTreeName.Movie);
// 'VodTreeName' only refers to a type, but is being used as a value here.
--or--
const VodTreeName = {
Movie: 'movie_vod',
TV: 'tv_vod',
VideoStore: 'video_store'
};
function test(something: keyof typeof VodTreeName) {
// expecting something === 'movie_vod'
}
test(VodTreeName.Movie);
// Argument of type 'string' is not assignable to parameter of type '"Movie" | "TV" | "VideoStore"'.
How else can I do this without having a type AND an object that I have to export/import to other modules?
You cannot use a type alias in runtime, there's no js equivalent for that.
The test function in the 2nd snippet expects a key of VodTreeName but you are passing the value, it should be:
function test(key: keyof typeof VodTreeName) {
console.log(VodTreeName[key]);
}
test("Movie");
If you want to use it like so:
test(VodTreeName.Movie);
Then you're basically looking for a string based enum, in which case check this thread: Create an enum with string values in Typescript and this issue: Proposal: String enums.