简体   繁体   English

Elm Json.Decode.Pipeline 无法解码现有字段

[英]Elm Json.Decode.Pipeline fails decoding existing field

I have an elm decoder and a test for it.我有一个榆树解码器和一个测试。 The test is passing, but when I use the decoder on the Application, I get a strange error that does not seem consistent with the data passed.测试通过,但是当我在应用程序上使用解码器时,出现一个奇怪的错误,似乎与通过的数据不一致。 I cannot figure out what is wrong.我无法弄清楚出了什么问题。 For disclosure, I am new to Elm and this is my first application with it, that is targeted to production.为了披露,我是 Elm 的新手,这是我的第一个应用程序,它针对生产。


The decoder解码器

type alias ProviderData =
    { uid : String
    , displayName : Maybe String
    , photoURL : Maybe String
    , email : String
    , phoneNumber : Maybe String
    , providerId : String
    }

type alias TokenManager =
    { apiKey : String
    , refreshToken : String
    , accessToken : String
    , expirationTime : Time.Posix
    }

type alias User =
    { uid : String
    , displayName : Maybe String
    , photoURL : Maybe String
    , email : String
    , emailVerified : Bool
    , phoneNumber : Maybe String
    , isAnonymous : Bool
    , tenantId : Maybe String
    , providerData : List ProviderData
    , apiKey : String
    , appName : String
    , authDomain : String
    , stsTokenManager : TokenManager
    , redirectEventId : Maybe String
    , lastLoginAt : Time.Posix
    , createdAt : Time.Posix
    }

type alias Model =
    { isUserSignedIn : Bool
    , isWaitingForSignInLink : Bool
    , user : Maybe User
    }

unauthenticatedUser : Model
unauthenticatedUser =
    { isUserSignedIn = False
    , isWaitingForSignInLink = False
    , user = Nothing
    }

decoder : Json.Value -> Model
decoder json =
    case Json.decodeValue authDecoder (Debug.log ("Decoding " ++ Json.Encode.encode 4 json) json) of
        Ok model ->
            model

        Err err ->
            Debug.log (Debug.toString err) unauthenticatedUser

authDecoder : Json.Decoder Model
authDecoder =
    Json.succeed Model
        |> required "isUserSignedIn" Json.bool
        |> required "isWaitingForSignInLink" Json.bool
        |> optional "user" (Json.map Just decodeUser) Nothing

decodeUser : Json.Decoder User
decodeUser =
    Json.succeed User
        |> required "uid" Json.string
        |> optional "displayName" (Json.map Just Json.string) Nothing
        |> optional "photoURL" (Json.map Just Json.string) Nothing
        |> required "email" Json.string
        |> required "emailVerified" Json.bool
        |> optional "phoneNumber" (Json.map Just Json.string) Nothing
        |> required "isAnonymous" Json.bool
        |> optional "tenantId" (Json.map Just Json.string) Nothing
        |> required "providerData" (Json.list decodeProviderData)
        |> required "apiKey" Json.string
        |> required "appName" Json.string
        |> required "authDomain" Json.string
        |> required "stsTokenManager" decodeTokenManager
        |> optional "redirectEventId" (Json.map Just Json.string) Nothing
        |> required "lastLoginAt" timestampFromString
        |> required "createdAt" timestampFromString

decodeProviderData : Json.Decoder ProviderData
decodeProviderData =
    Json.succeed ProviderData
        |> required "uid" Json.string
        |> optional "displayName" (Json.map Just Json.string) Nothing
        |> optional "photoURL" (Json.map Just Json.string) Nothing
        |> required "email" Json.string
        |> optional "phoneNumber" (Json.map Just Json.string) Nothing
        |> required "providerId" Json.string

decodeTokenManager : Json.Decoder TokenManager
decodeTokenManager =
    Json.succeed TokenManager
        |> required "apiKey" Json.string
        |> required "refreshToken" Json.string
        |> required "accessToken" Json.string
        |> required "expirationTime" timestampFromInt

timestampFromString : Json.Decoder Time.Posix
timestampFromString =
    Json.andThen
        (\str ->
            case String.toInt str of
                Just ts ->
                    Json.succeed (Time.millisToPosix ts)

                Nothing ->
                    Json.fail (str ++ " is not a timetamp")
        )
        Json.string

timestampFromInt : Json.Decoder Time.Posix
timestampFromInt =
    Json.andThen
        (\ts ->
            Json.succeed (Time.millisToPosix ts)
        )
        Json.int

unauthenticatedUser : Model
unauthenticatedUser =
    { isUserSignedIn = False
    , isWaitingForSignInLink = False
    , user = Nothing
    }

The Error错误

Failure "Json.Decode.oneOf failed in the following 2 ways:



(1) Problem with the given value:
    
    {
            "uid": "",
            "displayName": null,
            "photoURL": null,
            "email": "joe@mail.com",
            "emailVerified": true,
            "phoneNumber": null,
            "isAnonymous": false,
            "tenantId": null,
            "providerData": [
                {
                    "uid": "joe@mail.com",
                    "displayName": null,
                    "photoURL": null,
                    "email": "joe@mail.com",
                    "phoneNumber": null,
                    "providerId": "password"
                }
            ],
            "apiKey": "",
            "appName": "[DEFAULT]",
            "authDomain": "example.firebaseapp.com",
            "stsTokenManager": {
                "apiKey": "",
                "refreshToken": "",
                "accessToken": "",
                "expirationTime": 1603998440000
            },
            "redirectEventId": null,
            "lastLoginAt": "1603576515267",
            "createdAt": "1603573117442",
            "multiFactor": {
                "enrolledFactors": []
            }
        }
    
    Expecting an OBJECT with a field named `createdAt`



(2) Problem with the given value:
    
    {
            "uid": "",
            "displayName": null,
            "photoURL": null,
            "email": "joe@mail.com",
            "emailVerified": true,
            "phoneNumber": null,
            "isAnonymous": false,
            "tenantId": null,
            "providerData": [
                {
                    "uid": "joe@mail.com",
                    "displayName": null,
                    "photoURL": null,
                    "email": "joe@mail.com",
                    "phoneNumber": null,
                    "providerId": "password"
                }
            ],
            "apiKey": "",
            "appName": "[DEFAULT]",
            "authDomain": "example.firebaseapp.com",
            "stsTokenManager": {
                "apiKey": "",
                "refreshToken": "",
                "accessToken": "",
                "expirationTime": 1603998440000
            },
            "redirectEventId": null,
            "lastLoginAt": "1603576515267",
            "createdAt": "1603573117442",
            "multiFactor": {
                "enrolledFactors": []
            }
        }
    
    Expecting null" <internals>: { isUserSignedIn = False, isWaitingForSignInLink = False, user = Nothing }

The Test考试

module Tests exposing (..)

import Expect
import Json.Decode as Json
import Modules.Firebase as Firebase
import Test exposing (..)
import Time


all : Test
all =
    describe "Test Firebase"
        [ test "Test auth JSON" <|
            \_ ->
                let
                    input =
                        """
                          { 
                              "isUserSignedIn": true,
                              "isWaitingForSignInLink": false,
                              "user": {
                                "uid": "xxxxxxxxxxxx",
                                "displayName": null,
                                "photoURL": null,
                                "email": "joe@mail.com",
                                "emailVerified": true,
                                "phoneNumber": null,
                                "isAnonymous": false,
                                "tenantId": null,
                                "providerData": [
                                  {
                                    "uid": "joe@mail.com",
                                    "displayName": null,
                                    "photoURL": null,
                                    "email": "joe@mail.com",
                                    "phoneNumber": null,
                                    "providerId": "password"
                                  }
                                ],
                                "apiKey": "apikey.xxxxxxxxx",
                                "appName": "[DEFAULT]",
                                "authDomain": "example.com",
                                "stsTokenManager": {
                                  "apiKey": "apikey.xxxxxxxxx",
                                  "refreshToken": "refresh.xxxxxxxxxxxx",
                                  "accessToken": "access.xxxxxxxxxxxx",
                                  "expirationTime": 1603825391000
                                },
                                "redirectEventId": null,
                                "lastLoginAt": "1603576515267",
                                "createdAt": "1603573117442",
                                "multiFactor": {
                                  "enrolledFactors": []
                                }
                              }
                          }
                        """

                    decodedOutput =
                        case Json.decodeString Json.value input of
                            Ok value ->
                                Ok (Firebase.decoder value)

                            Err err ->
                                Err err
                in
                Expect.equal decodedOutput
                    (Ok firebaseModel)
        ]

firebaseModel : Firebase.Model
firebaseModel =
    { isUserSignedIn = True
    , isWaitingForSignInLink = False
    , user =
        Just
            { uid = "xxxxxxxxxxxx"
            , displayName = Nothing
            , photoURL = Nothing
            , email = "joe@mail.com"
            , emailVerified = True
            , phoneNumber = Nothing
            , isAnonymous = False
            , tenantId = Nothing
            , providerData =
                [ { uid = "joe@mail.com"
                  , displayName = Nothing
                  , photoURL = Nothing
                  , email = "joe@mail.com"
                  , phoneNumber = Nothing
                  , providerId = "password"
                  }
                ]
            , apiKey = "apikey.xxxxxxxxx"
            , appName = "[DEFAULT]"
            , authDomain = "example.com"
            , stsTokenManager =
                { apiKey = "apikey.xxxxxxxxx"
                , refreshToken = "refresh.xxxxxxxxxxxx"
                , accessToken = "access.xxxxxxxxxxxx"
                , expirationTime = Time.millisToPosix 1603825391000
                }
            , redirectEventId = Nothing
            , lastLoginAt = Time.millisToPosix 1603576515267
            , createdAt = Time.millisToPosix 1603573117442
            }
    }

Edited at 2020-10-30编辑于 2020-10-30

Debug Info调试信息

Trying to debug this issue I started to incrementally build the model, field by field, to understand what was wrong.为了调试这个问题,我开始逐个字段地逐步构建模型,以了解出了什么问题。 The following model and decoder work fine:以下模型和解码器工作正常:

... 
type alias User =
    { uid : String
    , displayName : Maybe String
    , photoURL : Maybe String
    , email : String
    , emailVerified : Bool
    , phoneNumber : Maybe String
    , isAnonymous : Bool
    , tenantId : Maybe String
    , providerData : List ProviderData
    }
...
decodeUser : Json.Decoder User
decodeUser =
    Json.succeed User
        |> required "uid" Json.string
        |> optional "displayName" (Json.nullable Json.string) Nothing
        |> optional "photoURL" (Json.nullable Json.string) Nothing
        |> required "email" Json.string
        |> required "emailVerified" Json.bool
        |> optional "phoneNumber" (Json.nullable Json.string) Nothing
        |> required "isAnonymous" Json.bool
        |> optional "tenantId" (Json.nullable Json.string) Nothing
        |> required "providerData" (Json.list decodeProviderData)
...

Note 1- All the other code remains the same.注 1- 所有其他代码保持不变。

Note 2- The input data remains the same.注 2- 输入数据保持不变。

Note 3 - I changed Json.map Just to Json.nullable by @5ndG suggestion.注 3 - 我通过 @5ndG 建议将Json.map Just更改为Json.nullable I also tried optional "field" (Json.nullable Json.string) Nothing to required "field" (Json.nullable Json.string) but the issue persist.我还尝试了optional "field" (Json.nullable Json.string) Nothing required "field" (Json.nullable Json.string)但问题仍然存在。

With this change, the model gets to the expected state, minus the fields I removed.通过此更改,模型将达到预期状态,减去我删除的字段。 By adding the next field, |> required "apiKey" Json.string , it starts failing with the same issue.通过添加下一个字段|> required "apiKey" Json.string ,它开始以相同的问题失败。

I fixed this issue by making the first parameter a string and force my service to pass a JSON string instead of a Json.Decode.Value.我通过将第一个参数设为字符串并强制我的服务传递 JSON 字符串而不是 Json.Decode.Value 来解决此问题。

I change the decorator to:我将装饰器更改为:


decoder : String -> Model
decoder json =
    case Json.decodeString Json.value json of
        Ok value ->
            decodeValue value

        Err _ ->
            unauthenticatedUser


decodeValue : Json.Value -> Model
decodeValue json =
    case Json.decodeValue authDecoder json of
        Ok model ->
            model

        Err _ ->
            Debug.log unauthenticatedUser

Not sure why the can't I use the Json.Decode.Value from the service, but this solves the problem.不知道为什么我不能使用服务中的 Json.Decode.Value,但这解决了问题。

Edit: this answer is wrong, so ignore it.编辑:这个答案是错误的,所以忽略它。

I can reproduce your error with this input in the test:我可以在测试中使用此输入重现您的错误:

{ 
    "isUserSignedIn": true,
    "isWaitingForSignInLink": false
}

That is, miss out the user field entirely.也就是说,完全错过了user字段。

From reading the docs , I think this is a bug in the optional function.通过阅读文档,我认为这是optional功能中的一个错误。

As described here , you can fix this by replacing the user decoder with:如上所述在这里,您可以通过更换修复这个user用解码器:

       |> optional "user" (Json.nullable decodeUser) Nothing

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM