Το JSON (JavaScript Object Notation) είναι μια μορφή δεδομένων και αρχείων αναγνώσιμη από τον άνθρωπο, που χρησιμοποιείται συνήθως για την ανταλλαγή δεδομένων, ειδικά για εφαρμογές ιστού. Ως εκ τούτου, δεν αποτελεί έκπληξη το γεγονός ότι κατέχει ξεχωριστή θέση στη γλώσσα Elm.
Η Elm παρέχει τα βασικά modules Json.Decode και Json.Encode για την ανάλυση και τη συγγραφή δεδομένων JSON.
Οι αποκωδικοποιητές JSON είναι ένας τρόπος να δηλώσεις τι τύπο δεδομένων περιμένεις και σε ποια δομή δεδομένων πρέπει να αντιστοιχιστούν τα δεδομένα.
Ας εισάγουμε μερικά aliases και τύπους από τα modules:
import Json.Decode as Decode exposing (Decoder, Error)
import Json.Encode as Encode exposing (Value)
Χρησιμοποιώντας το Decode.decodeString : Decoder a -> String -> Result Error a με έναν αποκωδικοποιητή και μια συμβολοσειρά JSON, είτε θα πετύχει, αν η συμβολοσειρά συμφωνεί με τις προδιαγραφές του αποκωδικοποιητή, είτε θα αποτύχει.
Δεν υπάρχει τρόπος να επιθεωρήσεις τα ακατέργαστα δεδομένα JSON όταν ορίζεις τον αποκωδικοποιητή, κάτι που μπορεί να φαίνεται ασυνήθιστο αν προέρχεσαι από προστακτικές γλώσσες.
Το JSON έχει έναν μικρό αριθμό βασικών τύπων δεδομένων:
"hello world"
true είτε false
34 ή 0.12
null
Κάθε ένα από αυτά συνδέεται με μια συνάρτηση του Decode:
Decode.decodeString Decode.string """\"hello\""""
--> Ok "hello"
Decode.decodeString Decode.string """null"""
--> Err ...
Decode.decodeString Decode.bool """true"""
--> Ok True
Decode.decodeString Decode.bool """0"""
--> Err ...
Decode.decodeString Decode.int """12"""
--> Ok 12
Decode.decodeString Decode.float """3.14"""
--> Ok 3.14
Decode.decodeString (Decode.null AnyValue) """null"""
--> Ok AnyValue
Decode.decodeString (Decode.null AnyValue) """true"""
--> Err ...
Σημείωσε ότι το Decode.null σου επιτρέπει να αποφασίσεις πώς θα μοντελοποιήσεις ένα null στο πρόγραμμά σου, απαιτώντας μια αυθαίρετη τιμή ως όρισμα (ίσως (), Nothing, ή ό,τι ταιριάζει στο πρόγραμμά σου).
Το JSON ορίζει επίσης δύο δομές δεδομένων για τη συλλογή βασικών τύπων δεδομένων:
[0, null, "none", []]
{"id": 1, "is_admin": false, "more_info": {}}
Το Decode προσφέρει επίσης συναρτήσεις για την αποκωδικοποίησή τους:
Decode.decodeString (Decode.list Decode.int) """[1, 2, 3]"""
--> Ok [1, 2, 3]
Decode.decodeString (Decode.list Decode.int) """[1, 2, 3, "not an int"]"""
--> Err ...
Decode.decodeString (Decode.dict Decode.int) """{ "key1": 17, "key2": 71 }"""
--> Ok (Dict.fromList [("key1", 17), ("key2", 71)])
Decode.decodeString (Decode.dict Decode.int) """{ "key1": 17, "key2": "seventy-one" }"""
--> Err ...
Αυτές οι συναρτήσεις Decode.list : Decoder a -> Decoder (List a) και Decode.dict : Decoder a -> Decoder (Dict String a) περιμένουν η καθεμία έναν αποκωδικοποιητή ως όρισμα, ο οποίος αναμένεται να αποκωδικοποιήσει κάθε μεμονωμένο στοιχείο της δομής δεδομένων.
Αν ο αποκωδικοποιητής-όρισμα αποτύχει να αποκωδικοποιήσει οποιοδήποτε στοιχείο μιας τιμής array ή object, ολόκληρος ο αποκωδικοποιητής αποτυγχάνει, κάτι που είναι ένα συνηθισμένο μοτίβο.
Ο συνδυασμός απλών αποκωδικοποιητών σε πιο σύνθετους είναι η βασική ιδέα της τεχνικής. Θα παρουσιάσουμε αρκετές ακόμη συναρτήσεις που μπορούν να χρησιμοποιηθούν για να χτίσεις αποκωδικοποιητές δεδομένων του πραγματικού κόσμου, οσοδήποτε σύνθετους, μέσα από ένα παράδειγμα.
Για να εξασκηθείς στον συνδυασμό βασικών αποκωδικοποιητών ώστε να αναλύεις σύνθετα δεδομένα JSON, ας φτιάξουμε αποκωδικοποιητές για την ανάλυση (ενός υποσυνόλου) δεδομένων GeoJSON. Το GeoJSON είναι μια εξειδικευμένη μορφή δεδομένων βασισμένη στο JSON, που χρησιμοποιείται για την αναπαράσταση γεωγραφικών χαρακτηριστικών, όπως τοποθεσίες, διαδρομές ή περιοχές, με γεωγραφικές συντεταγμένες 2D ή 3D, μαζί με άλλες ιδιότητες.
Ένα Γεωμετρικό Αντικείμενο GeoJSON είναι ένα αντικείμενο που περιέχει ένα πεδίο type με τιμή συμβολοσειράς (υπάρχουν 7 πιθανές τιμές για το type) και ένα πεδίο coordinates με γεωγραφικές συντεταγμένες (συγκεντρωμένες σε μια δομή που εξαρτάται από τον τύπο).
Για παράδειγμα,
{
"type": "Point",
"coordinates": [127.831, 26.461]
}
ή
{
"type": "LineString",
"coordinates": [
[127.831, 26.461],
[127.829, 26.465],
[127.829, 26.469],
[127.83, 26.47]
]
}
Ένα Γεωμετρικό Αντικείμενο θα μπορούσε να αναπαρασταθεί στην Elm με τον ακόλουθο τύπο
type Geometry
= Point (List Float)
| LineString (List (List Float))
| ...
Για να ελέγξεις την τιμή ενός συγκεκριμένου πεδίου, μπορείς να χρησιμοποιήσεις το Decode.field : String -> Decoder a -> Decoder a, το οποίο αποκωδικοποιεί την τιμή ενός συγκεκριμένου κλειδιού ενός αντικειμένου.
decodePointCoordinates : Decoder (List Float)
decodePointCoordinates =
Decode.field "coordinates" (Decode.list Decode.float)
decodeLineStringCoordinates : Decoder (List (List Float))
decodeLineStringCoordinates =
Decode.field "coordinates" (Decode.list (Decode.list Decode.float))
Για να μετασχηματίσεις την τιμή που αναλύθηκε με επιτυχία από έναν αποκωδικοποιητή, μπορείς να χρησιμοποιήσεις το Decode.map : (a -> value) -> Decoder a -> Decoder value, ή οποιοδήποτε από τα αδέρφια του, από το Decode.map2 (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value μέχρι το Decode.map8.
Σε αυτή την περίπτωση, θέλουμε να αποφασίσουμε ποιον αποκωδικοποιητή συντεταγμένων θα χρησιμοποιήσουμε ανάλογα με την τιμή του πεδίου type, κάτι για το οποίο είναι ιδανικό το Decode.andThen : (a -> Decoder b) -> Decoder a -> Decoder b και οι φίλοι του Decode.succeed : a -> Decoder a και Decode.fail : String -> Decoder a.
decodeGeometry : Decoder Geometry
decodeGeometry =
Decode.field "type" Decode.string
|> Decode.andThen
(\value ->
case value of
"Point" ->
decodePointCoordinates |> Decode.map Point
"LineString" ->
decodeLineStringCoordinates |> Decode.map LineString
_ ->
Decode.fail "Geometry not implemented yet, or invalid type value"
)
Σημείωσε σε αυτό το σημείο ότι θα μπορούσαμε να χρησιμοποιήσουμε το Decode.andThen για να επικυρώσουμε τις συντεταγμένες (ελέγχοντας ότι οι συντεταγμένες έρχονται σε ζεύγη ή τριάδες και ότι οι τιμές των συντεταγμένων έχουν γεωγραφικό νόημα).
Decode.decodeString decodeGeometry """{"type": "Point", "coordinates": [127.831, 26.461]}"""
--> Ok (Point [127.831, 26.461])
Decode.decodeString decodeGeometry """{"type": "LineString", "coordinates": [127.831, 26.461]}"""
--> Err ...
Ένα Αντικείμενο Χαρακτηριστικού GeoJSON αναπαριστά κάτι με χωρικά όρια: έχει ένα πεδίο type με την τιμή "Feature", ένα πεδίο geometry με ένα Γεωμετρικό Αντικείμενο ή ένα null, ένα πεδίο properties με ένα αυθαίρετο JSON Object ή ένα null, και ένα προαιρετικό πεδίο id με τιμή συμβολοσειράς ή αριθμού JSON.
{
"type": "Feature",
"geometry": null,
"properties": null
}
ή
{
"type": "Feature",
"id": "0157",
"geometry": {
"type": "Point",
"coordinates": [127.831, 26.461]
},
"properties": {
"country": "Japan"
}
}
Ας ασχοληθούμε πρώτα με το πεδίο id.
Αν υπάρχει, πρέπει να χωράει συμβολοσειρές ή αριθμούς, οπότε θα μπορούσαμε είτε να εισαγάγουμε έναν προσαρμοσμένο τύπο για την τιμή, είτε να την αποθηκεύουμε πάντα ως συμβολοσειρά.
Για να επιλέξεις ανάμεσα σε δύο πιθανούς αποκωδικοποιητές, μπορείς να χρησιμοποιήσεις το Decode.oneOf : List (Decoder a) -> Decoder a.
decodeFeatureId : Decoder String
decodeFeatureId =
Decode.oneOf
[ Decode.string
, Decode.int |> Decode.map String.fromInt
, Decode.float |> Decode.map String.fromFloat
]
|> Decode.field "id"
Decode.decodeString decodeFeatureId """{"id": "seventeen"}"""
--> Ok "seventeen"
Decode.decodeString decodeFeatureId """{"id": 17}"""
--> Ok "17"
Decode.decodeString decodeFeatureId """{"type": "Point"}"""
--> Er ...
Για να εκφράσουμε ότι το πεδίο είναι προαιρετικό, θα μπορούσαμε να χρησιμοποιήσουμε την ίδια τεχνική
decodeMaybeFeatureId : Decoder (Maybe String)
decodeMaybeFeatureId =
Decode.oneOf
[ decodeFeatureId |> Decode.map Just
, Decode.succeed Nothing
]
Αλλά θα μπορούσαμε αντ' αυτού να χρησιμοποιήσουμε το Decode.maybe : Decoder a -> Decoder (Maybe a)
decodeMaybeFeatureId : Decoder (Maybe String)
decodeMaybeFeatureId =
Decode.maybe decodeFeatureId
Decode.decodeString decodeMaybeFeatureId """{"id": 17}"""
--> Ok (Just "17")
Decode.decodeString decodeMaybeFeatureId """{"type": "Point"}"""
--> Ok Nothing
Σημείωσε ότι το decodeMaybeFeatureId (ή οποιοσδήποτε αποκωδικοποιητής τυλιγμένος με Decode.maybe) θα πετύχει πάντα, αφού μπορεί πάντα να καταφύγει στο Ok Nothing, αν και ένας αποκωδικοποιητής που το χρησιμοποιεί μπορεί να αποτύχει
Decode.decodeString (Decode.list decodeMaybeFeatureId) """{"id": 17}"""
--> Err ...
Στη συνέχεια, ας επικεντρωθούμε στο πεδίο geometry, το οποίο μπορεί είτε να είναι ένα Γεωμετρικό Αντικείμενο είτε null.
Θα μπορούσαμε να χρησιμοποιήσουμε το Decode.oneOf για άλλη μια φορά, αλλά υπάρχει καλύτερη επιλογή: το Decode.nullable : Decoder a -> Decoder (Maybe a).
decodeFeatureGeometry : Decoder (Maybe Geometry)
decodeFeatureGeometry =
Decode.nullable decodeGeometry
|> Decode.field "geometry"
Decode.decodeString decodeFeatureGeometry """{"geometry": {"type": "Point", "coordinates": [127.831, 26.461]}}"""
--> Ok (Just (Point [127.831, 26.461]))
Decode.decodeString decodeFeatureGeometry """{"geometry": null}"""
--> Ok Nothing
Decode.decodeString decodeFeatureGeometry """{"geometry": {}}"""
--> Err ...
Τα Decode.maybe και Decode.nullable έχουν την ίδια υπογραφή, αλλά η συμπεριφορά τους είναι διαφορετική, αφού το αποτέλεσμα OK Nothing ενός Decode.nullable θα προέλθει μόνο από τη συγκεκριμένη τιμή JSON null.
Τέλος, το πεδίο properties μπορεί να περιέχει ένα αυθαίρετο JSON Object ή ένα null, κάτι που είναι δουλειά για το Decode.value : Decoder Value.
decodeProperties : Decoder (Maybe (Dict String Value))
decodeProperties =
Decode.nullable (Decode.dict Decode.value)
|> Decode.field "properties"
Decode.decodeString decodeProperties """{"properties": null}"""
--> Ok Nothing
Decode.decodeString decodeProperties """{"properties": {"country": "Japan"}}"""
--> Ok (Just (Dict.fromList [("country", <internals>)]))
Decode.decodeString decodeProperties """{"properties": 17}"""
--> Err ...
Σημείωσε ότι το Value είναι ένας αδιαφανής τύπος, που σημαίνει ότι δεν μπορείς εύκολα να εξετάσεις το περιεχόμενό του.
Για να το χρησιμοποιήσεις, μπορείς είτε να γράψεις έναν αποκωδικοποιητή γι' αυτό και να τον τρέξεις με το Decode.decodeValue : Decoder a -> Value -> Result Error a, είτε να το στείλεις απευθείας στη JavaScript μέσω ενός port.
Είμαστε έτοιμοι να αναλύσουμε Αντικείμενα Χαρακτηριστικού:
type alias Feature =
{ id : Maybe String
, geometry : Maybe Geometry
, properties : Maybe (Dict String Value)
}
decodeFeature : Decoder Feature
decodeFeature =
Decode.field "type" Decode.string
|> Decode.andThen
(\value ->
if value /= "Feature" then
Decode.fail "not a Feature"
else
Decode.map3 Feature
decodeMaybeFeatureId
decodeFeatureGeometry
decodeProperties
)
Decode.decodeString decodeFeature """{"type": "Feature", "geometry": null, "properties": null}"""
--> Ok { geometry = Nothing, id = Nothing, properties = Nothing }
"""
{
"type": "Feature",
"id": "0157",
"geometry": {
"type": "Point",
"coordinates": [127.831, 26.461]
},
"properties": {
"country": "Japan"
}
}
"""
|> Decode.decodeString decodeFeature
--> Ok { geometry = Just (Point [127.831,26.461]), id = Just "0157", properties = Just (Dict.fromList [("country",<internals>)]) }
Οι κωδικοποιητές επιτρέπουν να γράψεις έγκυρο JSON από τιμές της Elm χρησιμοποιώντας τη συνάρτηση Encode.encode : Int -> Value -> String.
Το πρώτο όρισμα καθορίζει το μέγεθος της εσοχής στο τελικό αποτέλεσμα, και το δεύτερο όρισμα είναι η τιμή JSON που θα γραφτεί.
Μια Value μπορεί είτε να προέλθει από το Decode.value είτε να παραχθεί από έναν από τους κωδικοποιητές:
Encode.encode 0 (Encode.string "hello")
--> "hello"
Encode.encode 0 (Encode.bool True)
--> "true"
Encode.encode 0 (Encode.int 12)
--> "12"
Encode.encode 0 (Encode.float 3.14)
--> "3.14"
Encode.encode 0 Encode.null
--> "null"
Encode.encode 0 (Encode.list Encode.int [1, 2, 3])
--> "[1,2,3]"
Encode.encode 4 (Encode.list Encode.int [1, 2, 3])
--> "[\n 1,\n 2,\n 3\n]"
Encode.encode 0 (Encode.dict String.toLower Encode.int (Dict.fromList [("KEY1", 17), ("KEY2", 71)]))
--> "{\"key1\":17,\"key2\":71}"
Encode.encode 4 (Encode.dict String.toLower Encode.int (Dict.fromList [("KEY1", 17), ("KEY2", 71)]))
--> "{\n \"key1\": 17,\n \"key2\": 71\n}"
καθώς και με το Encode.object : List ( String, Value ) -> Value
Encode.object
[ ( "key1", Encode.int 17 )
, ( "key2", Encode.int 71 )
]
|> Encode.encode 0
--> "{\"key1\":17,\"key2\":71}"
Ας ορίσουμε κωδικοποιητές για τους αποκωδικοποιητές GeoJSON που ορίσαμε νωρίτερα
encodeGeometry : Geometry -> Value
encodeGeometry geometry =
case geometry of
Point coord ->
Encode.object
[ ( "type", Encode.string "Point" )
, ( "coordinates", Encode.list Encode.float coord )
]
LineString coord ->
Encode.object
[ ( "type", Encode.string "LineString" )
, ( "coordinates", Encode.list (Encode.list Encode.float) coord )
]
encodeFeature : Feature -> Value
encodeFeature feature =
let
maybeId =
case feature.id of
Nothing ->
[]
Just id ->
[ ( "id", Encode.string id ) ]
in
Encode.object
(maybeId
++ [ ( "type", Encode.string "Feature" )
, ( "geometry"
, case feature.geometry of
Nothing ->
Encode.null
Just geometry ->
encodeGeometry geometry
)
, ( "properties"
, case feature.properties of
Nothing ->
Encode.null
Just dict ->
Encode.dict identity identity dict
)
]
)
Γενικά, ένας καλός έλεγχος για τα ζεύγη αποκωδικοποιητή-κωδικοποιητή σου είναι να επιβεβαιώσεις ότι elmValue |> encoder |> decoder == elmValue.
Όσο περισσότερο μαθαίνεις για τον προγραμματισμό, τόσο πιο πολύ αρχίζεις να σκέφτεσαι ότι αυτό μπορεί να μην είναι απλώς μια παροδική μόδα. Μπορεί μάλιστα να υπάρχουν και επιχειρηματικές ευκαιρίες: οι άνθρωποι γράφουν κώδικα, θέλουν να τον αποθηκεύσουν, να τον μοιραστούν, να συνεργαστούν, ίσως ακόμη και να πάρουν κάποια ανατροφοδότηση.
Εντάξει, ας το κάνουμε, ας φτιάξουμε μια υπηρεσία που να ενθαρρύνει τους ανθρώπους να γράφουν κώδικα: hup, hup, κόσμε, ώρα να αρχίσει ο καθένας να χρησιμοποιεί το git! Ας το ονομάσουμε... GitHup.
Ξεκινάς να φτιάχνεις ένα REST JSON API: χρήστες, pull requests, σχόλια, και το backend ετοιμάζεται γρήγορα. Ορίστε ένα παράδειγμα του payload που επιστρέφεται όταν ζητάς από τον server τη λίστα με τα σχόλια σε ένα pull request:
[
{
"id": 256,
"pull_request_review_id": 42,
"user": {
"id": 101,
"login": "octodog",
"avatar_url": "https://githup.com/images/error/octodog_happy.gif",
"site_admin": false
},
"body": "Great stuff!",
"side": "RIGHT",
"_links": {
"self": {
"href": "https://api.githup.com/repos/octodog/Hello-World/pulls/comments/1"
},
"html": {
"href": "https://githup.com/octodog/Hello-World/pull/1#discussion-diff-1"
},
"pull_request": {
"href": "https://api.githup.com/repos/octodog/Hello-World/pulls/1"
}
}
},
{
"id": 11,
"pull_request_review_id": null,
"user": {
"id": 2,
"login": "hexacat",
"name": "Alex Kate",
"avatar_url": "https://githup.com/images/error/hexacat_happy.gif",
"site_admin": true
},
"body": "Amazing stuff!",
"side": "LEFT",
"_links": {
"self": {
"href": "https://api.githup.com/repos/octodog/Hello-World/pulls/comments/1"
}
}
}
]
Φυσικά, το frontend θα γίνει σε Elm, αλλά πρώτα πρέπει να καταλάβεις περί τίνος πρόκειται αυτοί οι διαβόητοι decoders/encoders.
Πέρα από το χιουμοριστικό σενάριο, οι απαιτήσεις της άσκησης προέρχονται απευθείας από τα REST API endpoints του GitHub για σχόλια σε pull request. Κάποια πεδία αφαιρέθηκαν από τα JSON schemas για λόγους απλότητας, αλλά αυτό είναι όσο πιο κοντά γίνεται σε εφαρμογές του πραγματικού κόσμου.
Το αετίσιο μάτι σου παρατήρησε ότι τόσο τα σχόλια όσο και οι χρήστες έχουν ένα πεδίο id, ιδανικό σημείο για να ξεκινήσεις.
Όρισε τη decodeId ώστε να μπορεί να αποκωδικοποιεί ένα JSON αντικείμενο, με έναν ακέραιο id.
Decode.decodeString decodeId """{id: 10}"""
--> Ok 10
Παρατηρείς στο παραπάνω παράδειγμα ότι δεν έχουν όλοι οι χρήστες όνομα.
Όρισε τη decodeName ώστε να μπορεί να αποκωδικοποιεί μια συμβολοσειρά στο πεδίο name, αν υπάρχει.
Δεν υπάρχει όνομα; Κανένα πρόβλημα, απλώς επέστρεψε Nothing.
Αυτός ο decoder δεν πρέπει να αποτυγχάνει ποτέ.
Decode.decodeString decodeName """{"id": 10, "name": "Otto"}"""
--> Ok (Just Otto)
Decode.decodeString decodeName """null"""
--> Ok Nothing
Έχουμε φτάσει σχεδόν στη μέση όσον αφορά την αποκωδικοποίηση των χρηστών, ας το τελειώσουμε.
Όρισε τη decodeUser ώστε να μπορεί να αποκωδικοποιεί ένα JSON αντικείμενο χρήστη.
Φρόντισε να χρησιμοποιήσεις τις decodeId και decodeName μέσα στη decodeUser.
Decode.decodeString decodeUser
"""
{
"id": 101,
"login": "octodog",
"avatar_url": "https://githup.com/images/error/octodog_happy.gif",
"site_admin": false
}
"""
--> Ok
--> { id = 101
--> , name = Nothing
--> , login = "octodog"
--> , avatarUrl = "https://githup.com/images/error/octodog_happy.gif
--> , siteAdmin = False
--> }
Κάποια σχόλια σε ένα pull review είναι αυτόνομα και επομένως δεν έχουν pull_request_review_id.
Ωστόσο, οι προδιαγραφές αναφέρουν ότι σε μια τέτοια περίπτωση θα πρέπει να υπάρχει ακόμη ένα πεδίο pull_request_review_id, αλλά η τιμή του θα πρέπει να είναι null, το Nothing του φτωχού.
Όρισε τη decodePullRequestReviewId ώστε να μπορεί να αποκωδικοποιεί έναν ακέραιο στο πεδίο pull_request_review_id, αν υπάρχει.
Αν δεν υπάρχει ακέραιος, θα πρέπει να υπάρχει ένα null, και αν δεν υπάρχει null, ο decoder θα πρέπει να αποτυγχάνει.
Decode.decodeString decodePullRequestReviewId """{"pull_request_review_id": 3}"""
--> Ok (Just 3)
Decode.decodeString decodePullRequestReviewId """{"pull_request_review_id": null}"""
--> Ok Nothing
Decode.decodeString decodePullRequestReviewId """{"id": 3}"""
--> Err ...
Στη συνέχεια είναι αυτό το ενδιαφέρον πεδίο side.
Σε ένα pull review, μπορείς να σχολιάσεις κώδικα που αφαιρέθηκε (στην πλευρά Left της οθόνης) ή κώδικα που προστέθηκε (στην πλευρά Right της οθόνης).
Όρισε τη decodeSide ώστε να μπορεί να αποκωδικοποιεί είτε "LEFT" είτε "RIGHT" στο πεδίο side και να τα αντιστοιχίζει στην αντίστοιχη παραλλαγή τύπου.
Decode.decodeString decodeSide """{"side": "LEFT"}"""
--> Ok Left
Decode.decodeString decodeSide """{"side": "middle?"}"""
--> Err ...
Κάθε σχόλιο έχει ένα σύνολο συνδέσμων, που δεν φαίνεται να είναι το ίδιο σε όλα τα σχόλια.
Όρισε τη decodeLinks ώστε να μπορεί να αποκωδικοποιεί μια συλλογή από URLs στο πεδίο _links.
Τα ονόματα και ο αριθμός των συνδέσμων μπορεί να διαφέρουν, φρόντισε να το λάβεις υπόψη.
Οι ίδιοι οι σύνδεσμοι είναι JSON αντικείμενα που θα πρέπει να έχουν πάντα ένα πεδίο href.
Decode.decodeString decodeLinks """
{
"_links": {
"self": { "href": "https://api.githup.com/repos/octodog/Hello-World/pulls/comments/1" },
"html": { "href": "https://githup.com/octodog/Hello-World/pull/1#discussion-diff-1" },
"pull_request": { "href": "https://api.githup.com/repos/octodog/Hello-World/pulls/1" }
}
}
"""
--> Ok
--> (Dict.fromList
--> [ ( "self", "https://api.githup.com/repos/octodog/Hello-World/pulls/comments/1" )
--> , ( "html", "https://githup.com/octodog/Hello-World/pull/1#discussion-diff-1" )
--> , ( "pull_request", "https://api.githup.com/repos/octodog/Hello-World/pulls/1" )
--> ]
--> )
Σχεδόν φτάσαμε, ας το τελειώσουμε.
Όρισε τις decodeComment και decodeComments ώστε να μπορούν να αποκωδικοποιούν ένα μεμονωμένο JSON αντικείμενο σχολίου ή μια λίστα με σχόλια αντίστοιχα.
Φρόντισε να χρησιμοποιήσεις όλες τις συναρτήσεις που έχεις ορίσει μέχρι τώρα στη decodeComment, με εξαίρεση τη decodeName.
Φυσικά, η decodeComments θα πρέπει να χρησιμοποιεί και τη decodeComment.
Λείπει ακόμη ένα χαρακτηριστικό από όλα αυτά: η δυνατότητα να στέλνεις ένα νέο σχόλιο στον server.
Γι' αυτό, πρέπει να πετύχεις το αντίθετο ενός decoder, πρέπει να μπορείς να μετατρέπεις ένα Elm Comment σε ένα JSON Value.
Όρισε τη encodeComment ώστε να μπορεί να κωδικοποιεί οποιοδήποτε έγκυρο Comment.
Η σειρά των πεδίων δεν έχει σημασία, αρκεί ο encoder να παράγει μια τιμή που μπορεί να αποκωδικοποιήσει η decodeComment.
Γράψου στο Exercism για να μάθεις και να κατακτήσεις Elm με 28 έννοιες110 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.