{"record":{"id":"650c915815614bb1","repo":"larksuite/cli","slug":"custom-json-type-s-requires-an-explicit-shape","errorCode":null,"errorMessage":"custom JSON type %s requires an explicit Shape","messagePattern":"custom JSON type (.+?) requires an explicit Shape","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shortcuts/common/typed_compile_data.go","lineNumber":154,"sourceCode":"\tcase reflect.Slice, reflect.Array:\n\t\tif baseType == jsonRawMessageType {\n\t\t\treturn nil, fmt.Errorf(\"json.RawMessage requires an explicit Shape\")\n\t\t}\n\t\tif baseType.Elem().Kind() == reflect.Uint8 {\n\t\t\treturn nil, fmt.Errorf(\"byte slice or array %s requires an explicit Shape\", baseType)\n\t\t}\n\t\tif len(schema.enum) > 0 || hasStringConstraints(schema) || hasNumberConstraints(schema) || schema.format != \"\" {\n\t\t\treturn nil, fmt.Errorf(\"array field has incompatible schema constraint\")\n\t\t}\n\t\telementSchema := schemaTag{required: true}\n\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{}}}","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/shortcuts/common/typed_compile_data.go#L136-L172","documentation":"Struct fields whose types implement custom JSON encoding (json.Marshaler or encoding.TextMarshaler, e.g. time.Time, json.Number-style wrappers, big.Int) are rejected: the compiler cannot infer the wire schema from a MarshalJSON method, since arbitrary bytes could be emitted. An explicit Shape or a plain struct is required.","triggerScenarios":"A Data struct field whose type implements json.Marshaler or encoding.TextMarshaler (time.Time, url.URL, big.Int, custom ID types) hits shapeForType's reflect.Struct case during registration.","commonSituations":"Using time.Time directly for date fields; domain types with custom MarshalJSON reused as Data fields; generated types that embed custom encoders.","solutions":["Replace the field with the encoded representation, e.g. use `string` for a time.Time (RFC 3339) and format the value before assigning.","Provide an explicit Output.Data.Shape that declares the field as a string/number shape matching the encoding.","If the type is structurally simple, remove the MarshalJSON/UnmarshalJSON methods (or use a plain alias struct) so the compiler can walk it."],"exampleFix":"// before\ntype Data struct {\n    CreatedAt time.Time `json:\"created_at\"`\n}\n\n// after\ntype Data struct {\n    CreatedAt string `json:\"created_at\"` // t.Format(time.RFC3339)\n}","handlingStrategy":"validation","validationCode":"func implementsCustomEncoding(t reflect.Type) bool {\n    for t.Kind() == reflect.Pointer { t = t.Elem() }\n    return reflect.PointerTo(t).Implements(reflect.TypeFor[json.Marshaler]()) ||\n        reflect.PointerTo(t).Implements(reflect.TypeFor[encoding.TextMarshaler]())\n}\n// reject any Data struct field whose type satisfies implementsCustomEncoding","typeGuard":"func isCustomJSONType(t reflect.Type) bool {\n    return reflect.PointerTo(t).Implements(reflect.TypeFor[json.Marshaler]())\n}","tryCatchPattern":null,"preventionTips":["Store timestamps as RFC 3339 strings, not time.Time, in wire Data structs.","Keep MarshalJSON-bearing domain types out of typed Data; convert to plain fields first.","Check new field types with implementsCustomEncoding before adding them to Data."],"tags":["go","custom-marshaler","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"}