Elm Json.Decode.Pipeline 解码现有字段失败
Elm Json.Decode.Pipeline fails decoding existing field
我有一个 elm 解码器和一个测试。测试通过了,但是当我在应用程序上使用解码器时,我得到一个奇怪的错误,似乎与通过的数据不一致。我不知道出了什么问题。作为披露,我是 Elm 的新手,这是我的第一个应用程序,它是针对生产的。
解码器
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
}
错误
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 }
测试
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
}
}
编辑于 2020-10-30
调试信息
为了调试这个问题,我开始逐个字段地逐步构建模型,以了解问题所在。以下模型和解码器工作正常:
...
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)
...
注1-所有其他代码保持不变。
注2- 输入数据保持不变。
注 3 - 我根据@5ndG 的建议将 Json.map Just
更改为 Json.nullable
。我也试过 optional "field" (Json.nullable Json.string) Nothing
到 required "field" (Json.nullable Json.string)
但问题仍然存在。
通过此更改,模型达到预期状态,减去我删除的字段。通过添加下一个字段 |> required "apiKey" Json.string
,它开始因同样的问题而失败。
编辑:此答案错误,请忽略。
我可以在测试中用这个输入重现你的错误:
{
"isUserSignedIn": true,
"isWaitingForSignInLink": false
}
也就是说,完全错过 user
字段。
通过阅读 docs,我认为这是 optional
函数中的错误。
如 here 所述,您可以通过将 user
解码器替换为以下内容来解决此问题:
|> optional "user" (Json.nullable decodeUser) Nothing
我通过将第一个参数设为字符串并强制我的服务传递 JSON 字符串而不是 Json.Decode.Value.
来解决此问题
我把装饰器改成:
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
不确定为什么我不能使用服务中的 Json.Decode.Value,但这解决了问题。
我有一个 elm 解码器和一个测试。测试通过了,但是当我在应用程序上使用解码器时,我得到一个奇怪的错误,似乎与通过的数据不一致。我不知道出了什么问题。作为披露,我是 Elm 的新手,这是我的第一个应用程序,它是针对生产的。
解码器
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
}
错误
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 }
测试
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
}
}
编辑于 2020-10-30
调试信息
为了调试这个问题,我开始逐个字段地逐步构建模型,以了解问题所在。以下模型和解码器工作正常:
...
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)
...
注1-所有其他代码保持不变。
注2- 输入数据保持不变。
注 3 - 我根据@5ndG 的建议将 Json.map Just
更改为 Json.nullable
。我也试过 optional "field" (Json.nullable Json.string) Nothing
到 required "field" (Json.nullable Json.string)
但问题仍然存在。
通过此更改,模型达到预期状态,减去我删除的字段。通过添加下一个字段 |> required "apiKey" Json.string
,它开始因同样的问题而失败。
编辑:此答案错误,请忽略。
我可以在测试中用这个输入重现你的错误:
{
"isUserSignedIn": true,
"isWaitingForSignInLink": false
}
也就是说,完全错过 user
字段。
通过阅读 docs,我认为这是 optional
函数中的错误。
如 here 所述,您可以通过将 user
解码器替换为以下内容来解决此问题:
|> optional "user" (Json.nullable decodeUser) Nothing
我通过将第一个参数设为字符串并强制我的服务传递 JSON 字符串而不是 Json.Decode.Value.
来解决此问题我把装饰器改成:
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
不确定为什么我不能使用服务中的 Json.Decode.Value,但这解决了问题。