From 7f78199a057a00d32669a0c5988ec869d06eca64 Mon Sep 17 00:00:00 2001 From: Gilbert Ramirez Date: Fri, 2 Jan 2026 08:05:05 -0600 Subject: [PATCH 1/2] Add Json.Decode.andMap Use andMap to incrementally decode a JSON object. Each andMap provides a value that will be an argument to a type constructor. --- src/Json/Decode.gren | 34 +++++++++++++++++++-- tests/src/Test/Json.gren | 65 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/Json/Decode.gren b/src/Json/Decode.gren index 09dce07e..75ad1237 100644 --- a/src/Json/Decode.gren +++ b/src/Json/Decode.gren @@ -5,7 +5,7 @@ module Json.Decode exposing , maybe, oneOf , decodeString, decodeValue, Value, Error(..), errorToString , map, map2, map3, map4, map5, map6, map7, map8 - , lazy, value, null, succeed, fail, andThen + , lazy, value, null, succeed, fail, andThen, andMap ) {-| Turn JSON values into Gren values. We've inherited this from Elm. Definitely check out this [intro to @@ -44,7 +44,7 @@ JSON decoders][guide] to get a feel for how this library works! ## Fancy Decoding -@docs lazy, value, null, succeed, fail, andThen +@docs lazy, value, null, succeed, fail, andThen, andMap -} @@ -716,6 +716,36 @@ andThen = Gren.Kernel.Json.andThen +{-| Use andMap to incrementally decode a JSON object. Each andMap +provides a value that will be an argument to a type constructor. +For example: + +``` +-- The Type +type alias Info = + { height : Float + , age : Int + } + +-- The Constructor +makeInfo : Float -> Int -> Info +makeInfo height age = + { height = height + , age = age + } + +-- The Decoder +infoDecoder : Decoder Info +infoDecoder = + succeed makeInfo + |> andMap (field "height" float) + |> andMap (field "age" int) +``` +-} +andMap : Decoder a -> Decoder (a -> b) -> Decoder b +andMap = + map2 (|>) + {-| Sometimes you have JSON with recursive structure, like nested comments. You can use `lazy` to make sure your decoder unrolls lazily. diff --git a/tests/src/Test/Json.gren b/tests/src/Test/Json.gren index a3b0368f..f0c83619 100644 --- a/tests/src/Test/Json.gren +++ b/tests/src/Test/Json.gren @@ -13,6 +13,7 @@ tests = describe "Json decode" [ intTests , customTests + , andMapTests ] @@ -85,3 +86,67 @@ customTests = ++ Json.errorToString message in test "customDecoder preserves user error messages" <| \{} -> assertion + +-- Used for testing andMap +type alias TestRecord1 = + { stringField : String + , intField : Int + , boolField: Bool + , floatField: Float + , stringArrayField: Array String + } + +-- The constructor for TestRecord1 +mkTestRecord1: String -> Int -> Bool -> Float -> Array String -> TestRecord1 +mkTestRecord1 s i b f sa = + { stringField = s + , intField = i + , boolField = b + , floatField = f + , stringArrayField = sa + } + +andMapTests : Test +andMapTests = + let + jsonString = + """ + { + "stringField": "bar", + "intField": 42, + "boolField": true, + "floatField": 1.5, + "stringArrayField": [ "a", "b" ] + } + """ + + myDecoder = + Json.succeed mkTestRecord1 + |> Json.andMap (Json.field "stringField" Json.string) + |> Json.andMap (Json.field "intField" Json.int) + |> Json.andMap (Json.field "boolField" Json.bool) + |> Json.andMap (Json.field "floatField" Json.float) + |> Json.andMap (Json.field "stringArrayField" (Json.array Json.string)) + + parse = Json.decodeString myDecoder jsonString + + record = when parse is + Ok r -> r + Err _ -> mkTestRecord1 "" 0 False 0.0 [] + + in + describe "Json andMap " + [test "string field" <| + \{} -> Expect.equal record.stringField "bar" + , test "int field" <| + \{} -> Expect.equal record.intField 42 + , test "bool field" <| + \{} -> Expect.equal record.boolField True + , test "float field" <| + \{} -> Expect.within (Expect.Relative 0.001) record.floatField 1.5 + , test "string-array field" <| + \{} -> Expect.equalArrays record.stringArrayField [ "a", "b" ] + , test "parse error" <| + \{} -> Expect.ok parse + + ] From 672053f30d7b01bd59d2407318d7ac30c8ca126e Mon Sep 17 00:00:00 2001 From: Gilbert Ramirez Date: Sat, 3 Jan 2026 06:04:04 -0600 Subject: [PATCH 2/2] In the andMap documentation, fix the preformatted text example Use indentation instead of backticks in the MarkDown --- src/Json/Decode.gren | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/Json/Decode.gren b/src/Json/Decode.gren index 75ad1237..8550fee1 100644 --- a/src/Json/Decode.gren +++ b/src/Json/Decode.gren @@ -720,27 +720,26 @@ andThen = provides a value that will be an argument to a type constructor. For example: -``` --- The Type -type alias Info = - { height : Float - , age : Int - } - --- The Constructor -makeInfo : Float -> Int -> Info -makeInfo height age = - { height = height - , age = age - } - --- The Decoder -infoDecoder : Decoder Info -infoDecoder = - succeed makeInfo - |> andMap (field "height" float) - |> andMap (field "age" int) -``` + -- The Type + type alias Info = + { height : Float + , age : Int + } + + -- The Constructor + makeInfo : Float -> Int -> Info + makeInfo height age = + { height = height + , age = age + } + + -- The Decoder + infoDecoder : Decoder Info + infoDecoder = + succeed makeInfo + |> andMap (field "height" float) + |> andMap (field "age" int) + -} andMap : Decoder a -> Decoder (a -> b) -> Decoder b andMap =