{"record":{"id":"3967be2dba8b24dc","repo":"larksuite/cli","slug":"map-type-s-requires-an-explicit-shape","errorCode":null,"errorMessage":"map type %s requires an explicit Shape","messagePattern":"map type (.+?) requires an explicit Shape","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shortcuts/common/typed_compile_data.go","lineNumber":165,"sourceCode":"\t\telementShape, err := shapeForType(baseType.Elem(), elementSchema, input, active)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"array item: %w\", err)\n\t\t}\n\t\tshape = typedArrayShape{Items: elementShape, MinItems: schema.minItems, MaxItems: schema.maxItems}\n\tcase reflect.Struct:\n\t\tif implementsCustomEncoding(baseType) {\n\t\t\treturn nil, fmt.Errorf(\"custom JSON type %s requires an explicit Shape\", baseType)\n\t\t}\n\t\tif len(schema.enum) > 0 || hasStringConstraints(schema) || hasNumberConstraints(schema) || hasItemConstraints(schema) || schema.format != \"\" {\n\t\t\treturn nil, fmt.Errorf(\"object field has incompatible schema constraint\")\n\t\t}\n\t\tobject, err := compileStructShape(baseType, input, baseType.String(), active)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tshape = object\n\tcase reflect.Map:\n\t\treturn nil, fmt.Errorf(\"map type %s requires an explicit Shape\", baseType)\n\tcase reflect.Interface:\n\t\treturn nil, fmt.Errorf(\"interface type %s requires an explicit Shape\", baseType)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"Go type %s cannot be mapped to a ValueShape\", t)\n\t}\n\tif schema.nullable != nil && *schema.nullable {\n\t\tshape = typedOneOfShape{Variants: []typedValueShape{shape, typedNullShape{}}}\n\t}\n\treturn shape, nil\n}\n\n// compileStructShape walks one struct into an ObjectShape. active holds the\n// struct types already open on the current recursion path, so a type that\n// refers back to itself is reported as a compile error. Without the guard the\n// walk never terminates and the goroutine stack is exhausted -- that is a\n// fatal runtime error, not a panic, so no recover boundary can contain it and\n// the whole CLI dies during command registration.\n//","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/shortcuts/common/typed_compile_data.go#L147-L183","documentation":"Map types cannot be given a statically derived schema: their keys and values are not fixed at compile time, so the shape compiler cannot produce an ObjectShape or a constrained value shape. Fields typed as any map are rejected and require an explicit Shape or a concrete struct replacement.","triggerScenarios":"A Data struct field typed map[string]X, map[string]string, or similar is encountered by shapeForType's reflect.Map case during compileData/collectArgFields.","commonSituations":"Modeling dynamic key/value bags (labels, metadata, properties); porting from generic JSON code where maps were convenient; OpenAPI additionalProperties-style fields.","solutions":["Define a concrete struct with fixed, json-tagged fields instead of the map.","Provide an explicit Output.Data.Shape that describes the map's value shape.","If the content is truly dynamic, make the whole Data type `any` (anyJSONShape) and validate at runtime.","Encode known key sets as struct fields and keep only genuinely open content out of typed Data."],"exampleFix":"// before\ntype Data struct {\n    Labels map[string]string `json:\"labels\"`\n}\n\n// after\ntype Data struct {\n    Labels struct {\n        Env  string `json:\"env\"`\n        Team string `json:\"team\"`\n    } `json:\"labels\"`","handlingStrategy":"validation","validationCode":"func hasMapField(t reflect.Type) bool {\n    for t.Kind() == reflect.Pointer { t = t.Elem() }\n    switch t.Kind() {\n    case reflect.Map:\n        return true\n    case reflect.Struct:\n        for i := 0; i < t.NumField(); i++ {\n            if hasMapField(t.Field(i).Type) { return true }\n        }\n    case reflect.Slice, reflect.Array:\n        return hasMapField(t.Elem())\n    }\n    return false\n}","typeGuard":"func isMap(t reflect.Type) bool { return t.Kind() == reflect.Map }","tryCatchPattern":null,"preventionTips":["Replace dynamic maps with structs declaring all known keys with json tags.","Reserve `any` only for top-level Output.Data, not nested fields.","Validate Data struct types in a registration unit test before merging."],"tags":["go","map","schema","typed-data"],"backgroundTag":"explicit-shape-required","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}