Elm: decode local JSON file into dict - json

I'm working on my first Elm app.
I want to use a local JSON file as a lookup table. The file matches weather conditions (a string) to suggested clothing (a list).
EDIT (clarifying the question): Based on some earlier SO questions from 2015 I've tried using Http.get to get the contents and then Decode.dict to create the Dict. It seems strange to use an Http module to ingest a local file. Is this methodology, including the Http.send I use below correct in Elm?
I am also having a hard time finding an example of a decoder that would work for a JSON file like the one I have. Any pointers would be appreciated.
JSON
{
"male,10,overcast,light wind...": ["Winter Cap", "Light Jacket"...],
"female,30,partly cloudy...": ["Winter Cap", "Sunglasses", ...]
}
CODE
type alias ClothingDict =
Dict String List
clothingUrl: String
clothingUrl =
"./clothing.json"
getClothingDict : Cmd Msg
getClothingDict =
let
url = clothingUrl
in
Http.send SetClothing (Http.get url decodeClothingResponse)
type alias ClothingResponse =
{ clothingOptions: ClothingDict }
decodeClothingResponse : Decoder ClothingResponse
decodeClothingResponse =
Decode.dict ClothingResponse

Decode.dict or dict takes a Decoder in order to handle decoding the keys of the Dict. dict automatically extracts the keys as strings.
I converted the code to:
decodeClothingResponse : Decoder (Dict String (List String))
decodeClothingResponse =
dict (list string)
(list string) is a decoder that will decoder a list of string from json in to a List String

Related

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.

Printing out MongoDB Documents in JSON Format

I am retrieving documents from my mongoDB database, but I need to print it out in JSON format. I store all of the relevant information in an array as the MongoKitten documentation suggests. I want to print out the entire contents of each element in the array, which is remember a document from MongoDB. My code is as follows:
import Foundation
import MongoKitten
let myDatabase = try MongoKitten.Database("mongodb://taylor:starwars1#ds129374.mlab.com:29374/taylorswiftengine")
let myCollection = myDatabase["my_collection"]
Request.addHandler(forMethod: "GET", withRoute: "/:resource/:id1")
{
(routeParams:RouteParams) in
let myTopics = try! myCollection.find("topic" == "\(routeParams["id1"]!)")
let allTopics = Array(myTopics)
}
MongoKitten comes with an Extended JSON module, which you can import:
import ExtendedJSON
You can convert an array of documents ([Document]) to Extended JSON using makeExtendedJSON():
myArrayOfDocuments.makeExtendedJSON()
This returns a Cheetah.Value. Cheetah is OpenKittens JSON library. To get a JSON String, you can use the serializedString() method on Cheetah.Value.
To sum things up, use this to convert your array of BSON Documents to a JSON String:
myArrayOfDocuments.makeExtendedJSON().serializedString()
If your myTopics object a dictionary you could convert it to json like this:
let dict = ["key1": "B", "key2": "A", "key3": "C"]
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)
Otherwise you could manually convert the myTopics object to a [String:String] dictionary and than convert it to json like above but I'm not sure if thats the way to handle with mongodb objects.

Swift 3 parsing values from a json file

Swift 3 , Xcode8.2.1,
I'm trying to extract specific values from a json file in the project. The name of the file is city.list.json, and the syntax of the json file is as follows:
{"_id":707860,"name":"Hurzuf","country":"UA","coord":{"lon":34.283333,"lat":44.549999}}
{"_id":519188,"name":"Novinki","country":"RU","coord":{"lon":37.666668,"lat":55.683334}}
The input I have is the country name and i need the id value or the country code relevant returned as a string.
I get an error:
"Type 'Any?' has no subscript members",
The method I wrote:
private func findCountryCodeBy(location: String)->String{
var result:String="";
let bundle = Bundle(for: type(of: self));
if let theURL = bundle.url(forResource: "city.list", withExtension: "json") {
do {
let data = try Data(contentsOf: theURL);
if let parsedData = try? JSONSerialization.jsonObject(with: data, options:[]) as! [String:Any] {
result = parsedData["_id"][location][0] as! String;
}
} catch {
print(error);
result = "error";
}
}
return result;
}
That is not valid JSON. I think the nearest valid JSON equivalent would be EITHER a JSON list like:
[
{"_id":707860,"name":"Hurzuf","country":"UA","coord":{"lon":34.283333,"lat":44.549999}},
{"_id":519188,"name":"Novinki","country":"RU","coord":{"lon":37.666668,"lat":55.683334}}
]
Ie, a list enclosed within square brackets with each item separated by a comma.
OR a JSON dictionary:
{
"707860": {"name":"Hurzuf","country":"UA","coord":{"lon":34.283333,"lat":44.549999}},
"519188": {"name":"Novinki","country":"RU","coord":{"lon":37.666668,"lat":55.683334}}
}
Ie, a dictionary enclosed within curly brackets with the key (in this case I've used your _id as the key) before the : and the value (a dictionary of all the other items" after the :.
(Newlines, tabs, whitespace are ignored, I've just included them to make it obvious what I've done).
I think that the dictionary version may suit your code better, but it depends on what else you want to do with the data. A list may suit some situations better.
I wrote a quick Python script to simply read JSON from a file (and not do anything else with it), and it produced a parsing error for the not-quite-JSON that you had, but it worked fine on both of my JSON examples, above.
NB: If you do NOT have control over the format of the file you are reading (ie, if you are receiving it from some other source which cannot produce it in any other format) then you will have to either modify the format of the file after you receiv it to make it valid JSON, OR you will have to use something other than JSONSerialization to read it. You could modify it by replacing all occurrences of }{ or }\n{ with },{ and then put [ at the beginning and ] at the end. That should do the job for converting this particular file to valid JSON for a list. Converting to a dictionary would be a little more involved.
Ideally though, you may have control over the file format yourself, in which case, just change whatever generates the file to produce correct JSON in the first place.
Once you have your valid JSON and parsed it into your parsedData variable, you'll then need to fix this line:
result = parsedData["_id"][location][0] as! String;
Assuming that location is the the equivalent of the _id string in the JSON, then you may be able to use the dictionary version of the JSON above and replace that line with something like:
result = parsedData[location]["country"];
However, if location is not the _id string in the JSON, then you'd be better off using the list version of the JSON above, and use a for loop to compare the values of each list item (or use a dictionary version of the JSON keyed on whatever location actually relates to in the JSON).

How do you create a SwiftyJSON dictionary out of optional AnyObjects?

I recently ran into a very time-consuming issue for a simple task of creating a JSON dictionary with optional objects. I'm using SwiftyJSON.
If I create the following dictionary, I get thrown errors
JSON type doesn't have an initializer that supports Dictionary<String,AnyObject?>
But if I simply change AnyObject to an non-optional type, it works.
var dict: Dictionary <String, AnyObject?> = [
"title" : title as? AnyObject,
"date" : date as? AnyObject,
]
var jsonData: JSON = JSON(dict) // this is where I get the error
I need to actually a JSON data set that potentially has nil values, but it seems like SwiftyJSON doesn't allow it.
Is there any way I can create a JSON dictionary with optional objects using SwiftyJSON?
Neither any key nor any value of a Swift Dictionary can be nil.
You could declare the variable as optional
var dict: Dictionary <String, AnyObject>?

ServiceStack/Redis with Json over Http returns string not Json

I am trying to get CouchDB-like response from Redis - by using ServiceStack WebServices to access data stored via ServiceStack .Net RedisTypedClient onto Redis.
Now the web services are described as providing a CouchDB layer onto Redis. However, unlike equivalent CouchDB calls to a CouchDB, I never get back json but only strings - to be clear I do get back json but with the payload is in string format.
This applies to getting items from Redis List, Set and HashSet collections. All the items via either the xml,json or csv web services always deliver the payload as a string. Now I can see the serialized form of the type I stored in the string - such as a json array of strings or whatever, but the data is not delivered as json (or csv or xml) but as a string. I cannot find a query flag (.e.g. 'format=json' say) in any of the autogenerated documentation for these web services -which does say it delivers the payload as a string which is what I see.
Further apart from using the default jsv serializer through RedisTypedCLient I also tried directly calling the ServiceStack json serializer to serialize as json not jsv and also the Newtonsoft json serializer. None of this makes any difference. I did not expect it too as I imagine the default services would likely only manage the expected jsv version and would deliver anything else as strings. However I did not expect this of the internal serialization format.
So is it possible to get CouchDB like json responses from ServiceStack/Redis/Builtin-WebServices?
Update
Here is a typical query via the ServiceStack json web service:
http://myserver.com/redis/json/syncreply/GetAllItemsFromList?id=test
This is a Redis List collection containing strongly typed items:
type TestItem( gr,eType,pillar,offset) =
let mutable _gr = gr
let mutable _eType = eType
let mutable _pillar = pillar
let mutable _offset = offset
member x.Gr with get()= _gr and set(v) = _gr <- v
member x.EType with get()= _eType and set(v) = _eType <- v
member x.Pillar with get()= _pillar and set(v) = _pillar <- v
member x.Offset with get()= _offset and set(v) = _offset <- v
override x.ToString() = sprintf "%s %s %s %M" x.Gr x.EType x.Pillar x.Offset
The list collection was added using IRedisTypedClient Interface/API and I am expecting back a json list of json objects - a set of key/value pairs each pair corresponding to one of the four public properties in the type above. Instead I get
{"Items":[" {\"Gr\":\"BON13\",\"EType\":\"MARKET\",\"Pillar\":\"BON3.R0\",\"Offset\":0.0}","{\"Gr\":\"BOQ13\",\"EType\":\"MARKET\",\"Pillar\":\"BOQ3.R0\",\"Offset\":0.0}","{\"Gr\":\"BOU13\",\"EType\":\"MARKET\",\"Pillar\":\"BOU3.R0\",\"Offset\":0.0}","{\"Gr\":\"BOV13\",\"EType\":\"SETTLEPILLAR\",\"Pillar\":\"BOU3.R0\",\"Offset\":0.0}","{\"Gr\":\"BOZ16\",\"EType\":\"SETTLEPILLAR\",\"Pillar\":\"BOU3.R0\",\"Offset\":0.0}"],"ResponseStatus":{}}
In other words a string representation of the json object not the json object itself.
So again, how can I get back, in this case, a json list of of json objects rather than a json list of strings. (And the same goes for Sets, Dictionaries and more basic keyed json documents a la other NoSql dbs)?
I have the same issues getting back csv - it comes back as a string rather than either a csv of key/value pairs or a csv of keys and values and in XML where this,again, comes back as a string not not an XML format of key/value pairs.
Update 1
It does not need to be a strongly typed as above. It could be a list of a list of strings. In which case I get back a json list of strings rather than a json list containing items comprising json list of string.
Update 2
Whilst the problem clearly seems to be in the ServiceStack webservice implementation not being like CouchDB although it claims it is, here is some sample code to put the data into Redis via ServiceStack.
open System
open System.Collections.Generic
open ServiceStack.Redis
open System.Linq
type Repository() =
static let mutable __port = 6379
static let mutable __host = "myserver.com"
static let mutable __client = new RedisClient(__host,__port)
static member Client = __client :> IRedisClient
type Repository<'T>() =
let _client = Repository.Client
member x.GetList key =
use client = _client.As<'T>()
match _client.GetEntryType key with
| RedisKeyType.List ->
client.Lists.Item key |> client.GetAllItemsFromList
| _ -> new List<'T>()
member x.SetList (key, values: List<'T>) =
if (values.Count <> 0) then
use client = _client.As<'T>()
let list = client.Lists.Item key
values |> Seq.iter (fun x -> client.AddItemToList(list, x))
Usage
let repo = new Repository<List<string>>
let items = [["key0";"data0"];["key1";"data1"]]
|> Seq.map (fun kd -> List.init kd ))
|> List.init
repo.SetList("test",items)
The is just a cut and paste of longer code. I have tried this in c#, f# and with non default serialization as already stated. That is I have tried six different methods to date and none delivered the data payload as json objects via ServiceStack WebServices only as strings.
JSON data, when serialized and sent over the wire, is just a string. Your client needs to be JSON aware and de-serialize it into your object. The JsonClient for C# within ServiceStack is more than capable of handling this, and so are the many JavaScript frameworks that assist with AJAX calls (jQuery, AngularJS, etc).