Scala Lift MongoDB - Update MongoJsonObjectListField Compilation Error - json

I have the following:
def saveAnnotations(annotation: String) = {
var json = parse(annotation)
var data: List[AnnotationData] = json.extract[List[AnnotationData]]
Presentation.update(
("room" -> "demo-room"),
("$set" -> ("annotation" -> data))
)
}
Where the var "annotation" is a json object array string, e.g
[{"key": "val"},{"key": "val"}]
The field "annotation" is a MongoJsonObjectListField
When compiling I'm recieving the following error:
No implicit view available from (String, List[code.snippet.AnnotationData]) =>
net.liftweb.json.package.JValue.("$set" -> ("annotation" -> data))
^
I'm sure something simple is missing, any help is much appreciated, thanks in advance :)
EDIT
I've just noticed that it compiles if I do:
Presentation.update(
("room" -> "demo-room"),
("$set" -> ("annotation" -> ""))
)
However this obviously sets the annotation field value to an empty string, how would I force the annotation field to be overwritten with the json object array in the data var?

Related

Elm JSON Decoder for non-string JSON Array [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
I'm in the process of learning Elm and have run into a wall with JSON decoders. I am building an Elm frontend that I want to feed URLs from a backend API, which returns the following JSON array of URL strings:
["http://www.example.com?param=value","http://www.example2.com?param=value"]
If I try to parse this with a List String decoder in Elm, it fails because the above is not quoted or escaped as a string. My example decoder:
stringListDecoder : Json.Decode.Decoder (List String)
stringListDecoder = Json.Decode.list Json.Decode.string
myArrDec2 = D.list D.string
D.decodeString myArrDec2 ["http://www.example.com?param=value","http://www.example2.com?param=value"]
This fails because the JSON array is seen in Elm as a string list, as opposed to a flat string that the Json.Decode.decodeString function accepts. If I were to quote and escape the array to work with this decoder, it would be in this format:
"[\"http://www.example.com?param=value\",\"http://www.example2.com?param=value\"]"
How can I write an Elm decoder that can parse the unquote/unescaped JSON array in the format being returned by my API?
UPDATE
I think I failed to convey my question well enough; for that I apologize. The API is returning this value (which is valid JSON):
["http://www.example.com?param=value","http://www.example2.com?param=value"]
I'm grabbing this JSON with an Http.get and trying to run the decoder on the result, which fails because it is not a string.
-- HTTP
getUrls : Cmd Msg
getUrls =
Http.get
{ url = "http://127.0.0.1:3000/request" -- This is the endpoint returning ["http://www.example.com?param=value","http://www.example2.com?param=value"]
, expect = Http.expectJson GotUrls urlDecoder
}
urlDecoder : Decoder (List String)
urlDecoder =
Json.Decode.list Json.Decode.string
Since Elm cannot accept this JSON without escaping it as a string, could I use the Http.get call to convert it to one prior to parsing with my decoder? Or am I just missing something obvious due to my inexperience with Elm? Please let me know if I can clarify further and thank you for trying to help!
FINAL EDIT
It turns out after using Robert's excellent example that my own was failing due to an unrelated issue. Being new to Elm's HTTP package I was unaware of the Http.NetworkError msg, which Robert's Http error function handles. This led me to this question and ultimately revealed a CORS misconfiguration in the API.
Bare JSON cannot exist within Elm modules. When writing example JSON to be decoded in Elm, it must be wrapped in some other representation.
However, that representation is specific to Elm; it is not what the decoder expects to receive from the API.
Your decoder should work when the backend sends the literal JSON ["a","b"].
In code:
module Example exposing (..)
import Json.Decode as JD
stringListDecoder : JD.Decoder (List String)
stringListDecoder =
JD.list JD.string
{-| Bare JSON cannot exist inside Elm syntax; it must be wrapped in something
else. In this case, in a string.
-}
jsonFromApi : String
jsonFromApi =
"[ \"http://www.example.com?param=value\", \"http://www.example2.com?param=value\" ]"
decoded : Result JD.Error (List String)
decoded =
JD.decodeString stringListDecoder jsonFromApi
expectedResult : Result JD.Error (List String)
expectedResult =
Result.Ok [ "http://www.example.com?param=value", "http://www.example2.com?param=value" ]
decodesAsExpected : Bool
decodesAsExpected =
decoded == expectedResult
EDIT: Here's an example suitable to run with elm reactor:
src/request.json
["http://www.example.com?param=value","http://www.example2.com?param=value"]
src/Main.elm
module Main exposing (..)
import Browser
import Html exposing (Html, code, div, p, pre, span, text)
import Http
import Json.Decode as JD
type alias Model =
{ data : RemoteData Http.Error (List String) }
type RemoteData err a
= NotAsked
| Loading
| Loaded a
| Failed err
type Msg
= GotUrls (Result Http.Error (List String))
initialModel =
{ data = NotAsked }
getUrls : Cmd Msg
getUrls =
Http.get
{ url = "/src/request.json"
-- This is the endpoint returning ["http://www.example.com?param=value","http://www.example2.com?param=value"]
, expect = Http.expectJson GotUrls urlDecoder
}
urlDecoder : JD.Decoder (List String)
urlDecoder =
JD.list JD.string
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
GotUrls result ->
case result of
Ok strings ->
( { model | data = Loaded strings }, Cmd.none )
Err error ->
( { model | data = Failed error }, Cmd.none )
view : Model -> Html Msg
view model =
div []
(case model.data of
NotAsked ->
[ text "no data yet" ]
Loading ->
[ text "loading" ]
Failed err ->
[ text "Failed... ", show err ]
Loaded strings ->
strings |> List.map (\s -> p [] [ text s ])
)
show : Http.Error -> Html Msg
show error =
case error of
Http.BadUrl string ->
span [] [ text "Bad Url: ", text string ]
Http.Timeout ->
text "Timeout"
Http.NetworkError ->
text "Network error"
Http.BadStatus int ->
span [] [ text "Bad Status: ", int |> String.fromInt |> text ]
Http.BadBody string ->
span [] [ text "Bad Body: ", pre [] [ code [] [ text string ] ] ]
main : Program () Model Msg
main =
Browser.element
{ init = always ( initialModel, getUrls )
, view = view
, update = update
, subscriptions = always Sub.none
}

How do I make a for loop that scans through JSON in F#

Using F# I am trying to scan through a JSON file and compare its arrays against a single array of (randomly generated) numbers. The formatting for the json is:
{"1":[#,#,#,#,#],"2":[#,#,#,#,#],...}
etc for 121 entries. I'm currently trying Json.NET. My problems are:
How can I import a local file with Json.NET?
How would I set about making a simple call of the json key that'd return it's array value that's fit to run it through a for loop?
Here is my code of how far I've gotten:
open System
open System.IO
open Newtonsoft.Json
(*open FSharp.Data
type Provider = JsonProvider<"powernum.json">
let numbers = Provider.Load("powernum.json")
//numbers.``1`` gets me the array but can't scan through all the IDs with an iterating for loop
(*
if numbers.``3`` = [|29;30;41;48;64|] then
printfn "True"
else printfn "False"
*)
(*numbers.JsonValue.Item "1"
let test (a: int) = string a
let testPile =
for i = 1 to 10 do
let goNum = numbers.JsonValue.Item (test i)
Console.Write goNum
Console.WriteLine ""
testPile // This works but is not usable for data comparison with an F# Array
*)
*)
let r = new StreamReader("\PATH\powernum.json")
let (json: string) = r.ReadToEnd();
let conv = JsonConvert.DeserializeObject<> (json)
Console.WriteLine("{0}",conv)//where I'm stuck with Json.NET
[<EntryPoint>]
let main argv =
let rnd = Random()
let numberGen = Set.empty.Add(rnd.Next(1,69)).Add(rnd.Next(1,69)).Add(rnd.Next(1,69)).Add(rnd.Next(1,69)).Add(rnd.Next(1,69)) |>Set.toArray |>Array.sort
Console.ReadKey() |> ignore
0// return an integer exit code
Jsontocsharp.com renders invalid.
I've tried using F# Data but from what I've found it's impossible to make an iterating loop because I have to pull up the "key" with accents encapsulating the number to read it as an int like this numbers.``1``.It doesn't take variables. Tried another method while still using F# Data but it only works as a string that errors when I try to convert it.
For comparison this is the version I prototyped in python:
import random
import json
with open('/PATH/powernum.json') as pnumbers:
data = json.load(pnumbers)
#makes an array with the range of numbers
Valid_numbers =[]
for x in range(69):
Valid_numbers.append(x+1)
generated = []
def pulledNumber():
generated[:]=[]
#adds numbers to the array from 0-4
while len(generated) !=5:
#takes a random number from the range of numbers
generate_number = random.choice(Valid_numbers)
#check if the two arrays have the same values
if generate_number not in generated:
#add to the array if values don't match
generated.append(generate_number)
generated.sort()
return generated
pulledNumber()
for x, y in data.items():
if generated not in y:
print("Id: %s passed" % x)
else:
print("Id: %s failed" % x)
pulledNumber()
break
print (pulledNumber())
f# is a statically typed language - we simply often don't notice because of its excellent type inferencing. But when deserializing from a JSON file, before writing any code, it is useful to determine whether the JSON has a fixed schema, and if so, create or choose an appropriate data model to which the JSON can be mapped automatically.
In your case, your JSON looks like:
{
"1": [29,30,41,48,64],
"2": [29,320,441,548,11]
// Additional items omitted
}
When you have here is a root object with variable property names whose values are always an array of integers. As explained in the Newtonsoft documentation Deserialize a Dictionary, such a JSON object can be deserialized to some IDictionary<string, T> for appropriate value type T. And as explained in Deserialize a Collection a JSON array can be deserialized to a collection of an appropriate item type.
In f# we use Map<'Key,'Value> to represent a dictionary, and lists to represent a materialized list of statically typed values. Thus your JSON corresponds to a
Map<string, int list>
Having determined an appropriate data model, introduce the following function to deserialize JSON from a file:
//https://www.newtonsoft.com/json/help/html/DeserializeWithJsonSerializerFromFile.htm
let jsonFromFile<'T>(fileName : string, settings : JsonSerializerSettings) =
use streamReader = new StreamReader(fileName)
use jsonReader = new JsonTextReader(streamReader)
let serializer = JsonSerializer.CreateDefault(settings)
serializer.Deserialize<'T>(jsonReader)
Now you can deserialize your JSON and filter the values for lists matching some
let fileName = "\PATH\powernum.json"
let requiredValues = [29;30;41;48;64] // Or whatever list of values you are looking for
let map = jsonFromFile<Map<string, int list>>(fileName, null)
let filteredMap =
map |> Map.toSeq
|> Seq.filter (fun (key, value) -> requiredValues = value)
|> Map.ofSeq
// Print results
filteredMap |> Map.iter (fun key value ->
printf "Key = %A has matching list of values = %A\n" key value)
Which prints out
Key = "1" has matching list of values = [29; 30; 41; 48; 64]
Notes:
Always be sure to dispose of disposable resources such as StreamReader after you are done with them. The use keyword ensures this happens automatically when the resource goes out of scope.
If you would prefer to search for an unordered set of values, you can use set instead of list:
let requiredValues = set [64;29;30;41;48] // Or whatever set of values you are looking for
let map = jsonFromFile<Map<string, Set<int>>>(fileName, null)
let filteredMap =
map |> Map.toSeq
|> Seq.filter (fun (key, value) -> requiredValues = value)
|> Map.ofSeq
As explained in Equality and Comparison Constraints in F# by Don Syme, both list and set support structural equality comparisons, which is why requiredValues = value checks that the collections have identical contents.
Demo fiddle #1 here for list and #2 here for set.

How to encode a list of records to JSON in Reason?

Given a record type and a list of records:
type note = {
text: string,
id: string
};
let notes: list complete_note = [{text: "lol", id: "1"}, {text: "lol2", id: "2"}]
How do I encode this to JSON using bs-json module?
What I tried: I tried to manually create JSON string using string interpolation in bucklescript, but that's definitely not something I want to do :)
notes
|> Array.of_list
|> Array.map (
fun x => {
// What should I do?
}
)
|> Json.Encode.stringArray
|> Js.Json.stringify;
Disclaimer, I'm not a Reason expert, so the code might be non-idiomatic. It may also have errors, as I don't have the BuckleScript installed, so I didn't test it.
So, if you want to represent each note as a JSON object with text and id fields, then you can use the Js.Json.objectArray function to create a JSON document from an array of JS dictionaries. The easiest way to create a dictionary would be to use the Js.Dict.fromList function, that takes a list of pairs.
notes
|> Array.of_list
|> Array.map (fun {id, text} => {
Js.Dict.fromList [("text", Js.Json.string text), ("id", Js.Json.string id)]
})
|> Js.Json.objectArray
|> Js.Json.stringify;

Get case class and JSON object from a hierarchical JSON map with lift-json

I get maps such as:
Map(reference -> Map(scriptRenderings -> List(Map(text -> महा-सुभाषित-सङ्ग्रहे 9979, scheme -> null, startLetter -> म)), jsonClass -> QuoteText, metre -> None, key -> महा-सुभाषित-सङ्ग्रहे9979, language -> Map(code -> UNK)))
from my couchdb-lite database library.
I need to convert them into case classes I've defined. How do I accomplish this (preferably with lift-json, which I'm already using)?
And how to convert this map to a JSON object ? (Essentially reversing this.)
I ultimately ended up converting the map to string and parsing the string to the case class:
val jsonStr = Serialization.writePretty(jsonMap)
// log debug jsonStr
val quoteText = Serialization.read[QuoteText](jsonStr)

How to add a json object in to a json array using scala play?

In my scala code i have a json object consisting email data
val messages = inboxEmail.getMessages();
var jsonArray = new JsArray
for(inboxMessage <- messages)
{
...
...
val emailJson = Json.obj("fromAddress" -> fromAddressJsonList, "toAddress" -> toAddressJsonList, "ccAddress" -> ccAddressJsonList, "bccAddress" -> bccAddressJsonList, "subject" -> emailMessage.getSubject().toString(), "message" -> Json.toJson(emailMessageBody))
I need to add emailJson to the jsonArray during each loop
i tried
jsonArray.+:(emailJson)
and
jsonArray.append(emailJson)
but getting empty array
What should i use here to add jsonObject into the json array
Remember that JsArray is immutable, so writing
jsonArray.+:(emailJson)
will not modify jsonArray, it will just create a new json array with emailJson appended at the end.
Instead you would need to write something like:
val newArray = jsonArray +: emailJson
and use newArray instead of jsonArray afterwards.
In your case, you said you need to add an element "at each loop iteration". When using a functional language like Scala, you should probably try to think more in terms of "mapping over collections" rather than "iterating in a loop". For example you could write:
val values = messages map {inboxMessage =>
...
...
Json.obj("fromAddress" -> fromAddressJsonList, "toAddress" -> toAddressJsonList, "ccAddress" -> ccAddressJsonList, "bccAddress" -> bccAddressJsonList, "subject" -> emailMessage.getSubject().toString(), "message" -> Json.toJson(emailMessageBody))
}
val newArray = objects ++ JsArray(values)