Related
I have a list of dictionaries written to a data.txt file. I was expecting to be able to read the list of dictionaries in a normal way when I load, but instead, I seem to load up a string.
For example - when I print(data[0]), I was expecting the first dictionary in the list, but instead, I got "[" instead.
Below attached is my codes and txt file:
read_json.py
import json
with open('./data.txt', 'r') as json_file:
data = json.load(json_file)
print(data[0])
data.txt
"[
{
"name": "Disney's Mulan (Mandarin) PG13 *",
"cast": [
"Jet Li",
"Donnie Yen",
"Yifei Liu"
],
"genre": [
"Action",
"Adventure",
"Drama"
],
"language": "Mandarin with no subtitles",
"rating": "PG13 - Some Violence",
"runtime": "115",
"open_date": "18 Sep 2020",
"description": "\u201cMulan\u201d is the epic adventure of a fearless young woman who masquerades as a man in order to fight Northern Invaders attacking China. The eldest daughter of an honored warrior, Hua Mulan is spirited, determined and quick on her feet. When the Emperor issues a decree that one man per family must serve in the Imperial Army, she steps in to take the place of her ailing father as Hua Jun, becoming one of China\u2019s greatest warriors ever."
},
{
"name": "The New Mutants M18",
"cast": [
"Maisie Williams",
"Henry Zaga",
"Anya Taylor-Joy",
"Charlie Heaton",
"Alice Braga",
"Blu Hunt"
],
"genre": [
"Action",
"Sci-Fi"
],
"language": "English",
"rating": "M18 - Some Mature Content",
"runtime": "94",
"open_date": "27 Aug 2020",
"description": "Five young mutants, just discovering their abilities while held in a secret facility against their will, fight to escape their past sins and save themselves."
}
]"
The above list is formatted properly for easy reading but the actual file is a single line and the different lines are denoted with "\n". Thanks for any help.
remove double quote in data.txt is useful for me。
eg. modify
"[{...},{...}]"
to
[{...},{...}]
Hope it helps!
I am totally new in xamarin forms. I am trying to make an app that uses the TMDB api to display a list with movies but i am getting this exception:
Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[CinemaApp.Models.NowPlaying]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
This is my xaml file
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CinemaApp.PlayingNowPage">
<ContentPage.Content>
<AbsoluteLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<StackLayout x:Name="SLMovies" AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0,0,1,1">
<ListView x:Name="MoviesListView"
HasUnevenRows="True"
SeparatorVisibility="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Frame>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.4*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Source="{Binding full_poster_path}" Grid.Column="0"
HeightRequest="120"
WidthRequest="150"/>
<StackLayout Grid.Column="1">
<Label Text="{Binding title}"
TextColor="#E91E63"
FontAttributes="Bold"/>
<Label Text="{Binding vote_average}"/>
<Label Text="{Binding release_date}">
</Label>
<StackLayout
Orientation="Horizontal"
HorizontalOptions="FillAndExpand">
<Label HorizontalOptions="EndAndExpand"
Text="{Binding Duration}">
</Label>
</StackLayout>
</StackLayout>
</Grid>
</Frame>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
<StackLayout
x:Name="SLLoader"
IsVisible="True"
Padding="12"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="0.5,0.5,-1,-1">
<ActivityIndicator
IsRunning="True"
Color="#00000000"/>
<Label
Text="Loading..."
HorizontalOptions="Center"
TextColor="Red"/>
</StackLayout>
</AbsoluteLayout>
</ContentPage.Content>
</ContentPage>
This is the class for the objects
using System;
using System.Collections.Generic;
using System.Text;
namespace CinemaApp.Models
{
public class NowPlaying
{
public int id { get; set; }
public bool video { get; set; }
public double vote_average { get; set; }
public string title { get; set; }
public string poster_path { get; set; }
public string original_title { get; set; }
public string release_date { get; set; }
public string full_poster_path
{
get
{
if (string.IsNullOrEmpty(poster_path))
{
return string.Empty;
}
return string.Format("https://image.tmdb.org/t/p/w185/" +
poster_path);
}
}
}
}
And this the main class:
namespace CinemaApp
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class PlayingNowPage : ContentPage
{
public PlayingNowPage ()
{
InitializeComponent ();
GetMovies();
}
private async void GetMovies()
{
try
{
SLLoader.IsVisible = true;
HttpClient client = new HttpClient();
var response = await client.GetStringAsync(
"https://api.themoviedb.org/3/movie/now_playing?
api_key=XXXX&language=en-US&page=1");
var movies = JsonConvert.DeserializeObject<List<NowPlaying>>
(response);
Console.WriteLine("rere "+movies);
MoviesListView.ItemsSource = movies;
}
catch (Exception e)
{
Console.WriteLine("Error: " + e);
}
finally
{
SLLoader.IsVisible = false;
}
}
}
}
Your NowPlaying model only has the data for the object in one now playing result. If you look at the response, like in an example on the website: https://developers.themoviedb.org/3/movies/get-now-playing
{
"page": 1,
"results": [
{
"poster_path": "/e1mjopzAS2KNsvpbpahQ1a6SkSn.jpg",
"adult": false,
"overview": "From DC Comics comes the Suicide Squad, an antihero team of incarcerated supervillains who act as deniable assets for the United States government, undertaking high-risk black ops missions in exchange for commuted prison sentences.",
"release_date": "2016-08-03",
"genre_ids": [
14,
28,
80
],
"id": 297761,
"original_title": "Suicide Squad",
"original_language": "en",
"title": "Suicide Squad",
"backdrop_path": "/ndlQ2Cuc3cjTL7lTynw6I4boP4S.jpg",
"popularity": 48.261451,
"vote_count": 1466,
"video": false,
"vote_average": 5.91
},
{
"poster_path": "/lFSSLTlFozwpaGlO31OoUeirBgQ.jpg",
"adult": false,
"overview": "The most dangerous former operative of the CIA is drawn out of hiding to uncover hidden truths about his past.",
"release_date": "2016-07-27",
"genre_ids": [
28,
53
],
"id": 324668,
"original_title": "Jason Bourne",
"original_language": "en",
"title": "Jason Bourne",
"backdrop_path": "/AoT2YrJUJlg5vKE3iMOLvHlTd3m.jpg",
"popularity": 30.690177,
"vote_count": 649,
"video": false,
"vote_average": 5.25
},
{
"poster_path": "/tgfRDJs5PFW20Aoh1orEzuxW8cN.jpg",
"adult": false,
"overview": "Arthur Bishop thought he had put his murderous past behind him when his most formidable foe kidnaps the love of his life. Now he is forced to travel the globe to complete three impossible assassinations, and do what he does best, make them look like accidents.",
"release_date": "2016-08-25",
"genre_ids": [
80,
28,
53
],
"id": 278924,
"original_title": "Mechanic: Resurrection",
"original_language": "en",
"title": "Mechanic: Resurrection",
"backdrop_path": "/3oRHlbxMLBXHfMqUsx1emwqiuQ3.jpg",
"popularity": 20.375179,
"vote_count": 119,
"video": false,
"vote_average": 4.59
},
{
"poster_path": "/3ioyAtm0wXDyPy330Y7mJAJEHpU.jpg",
"adult": false,
"overview": "A high school senior finds herself immersed in an online game of truth or dare, where her every move starts to be manipulated by an anonymous community of \"watchers.\"",
"release_date": "2016-07-27",
"genre_ids": [
18,
53
],
"id": 328387,
"original_title": "Nerve",
"original_language": "en",
"title": "Nerve",
"backdrop_path": "/a0wohltYr7Tzkgg2X6QKBe3txj1.jpg",
"popularity": 7.17729,
"vote_count": 86,
"video": false,
"vote_average": 6.84
},
{
"poster_path": "/3S7V2Jd2G61LltoCsYUj4GwON5p.jpg",
"adult": false,
"overview": "A woman with a seemingly perfect life - a great marriage, overachieving kids, beautiful home, stunning looks and still holding down a career. However she's over-worked, over committed and exhausted to the point that she's about to snap. Fed up, she joins forces with two other over-stressed moms and all go on a quest to liberate themselves from conventional responsibilities, going on a wild un-mom like binge of freedom, fun and self-indulgence - putting them on a collision course with PTA Queen Bee Gwendolyn and her clique of devoted perfect moms.",
"release_date": "2016-07-28",
"genre_ids": [
35
],
"id": 376659,
"original_title": "Bad Moms",
"original_language": "en",
"title": "Bad Moms",
"backdrop_path": "/l9aqTBdafSo0n7u0Azuqo01YVIC.jpg",
"popularity": 6.450367,
"vote_count": 107,
"video": false,
"vote_average": 5.49
},
{
"poster_path": "/sRxazAAodkAWVPJighRAsls2zCo.jpg",
"adult": false,
"overview": "A falsely accused nobleman survives years of slavery to take vengeance on his best friend who betrayed him.",
"release_date": "2016-08-17",
"genre_ids": [
12,
36,
18
],
"id": 271969,
"original_title": "Ben-Hur",
"original_language": "en",
"title": "Ben-Hur",
"backdrop_path": "/A4xbEpe9LevQCdvaNC0z6r8AfYk.jpg",
"popularity": 6.379067,
"vote_count": 60,
"video": false,
"vote_average": 3.83
},
{
"poster_path": "/aRRLpsusORQxOpFkZvXdk00TkoY.jpg",
"adult": false,
"overview": "Nate Foster, a young, idealistic FBI agent, goes undercover to take down a radical white supremacy terrorist group. The bright up-and-coming analyst must confront the challenge of sticking to a new identity while maintaining his real principles as he navigates the dangerous underworld of white supremacy. Inspired by real events.",
"release_date": "2016-08-19",
"genre_ids": [
80,
18,
53
],
"id": 374617,
"original_title": "Imperium",
"original_language": "en",
"title": "Imperium",
"backdrop_path": "/9dMvJJ0eTVetq3kLwUXcphsY5H.jpg",
"popularity": 5.855316,
"vote_count": 33,
"video": false,
"vote_average": 6.05
},
{
"poster_path": "/4pUIQO6OqbzxrFLGMDf2dlplSR9.jpg",
"adult": false,
"overview": "Southside With You chronicles a single day in the summer of 1989 when the future president of the United States, Barack Obama, wooed his future First Lady on an epic first date across Chicago's South Side.",
"release_date": "2016-08-26",
"genre_ids": [
10749,
18
],
"id": 310888,
"original_title": "Southside With You",
"original_language": "en",
"title": "Southside With You",
"backdrop_path": "/fukREcpoPugi0yx6cVrFvsR7JBE.jpg",
"popularity": 5.229414,
"vote_count": 13,
"video": false,
"vote_average": 3.12
},
{
"poster_path": "/wJXku1YhMKeuzYNEHux7XtaYPsE.jpg",
"adult": false,
"overview": "Based on a true story, “War Dogs” follows two friends in their early 20s living in Miami during the first Iraq War who exploit a little-known government initiative that allows small businesses to bid on U.S. Military contracts. Starting small, they begin raking in big money and are living the high life. But the pair gets in over their heads when they land a 300 million dollar deal to arm the Afghan Military—a deal that puts them in business with some very shady people, not the least of which turns out to be the U.S. Government.",
"release_date": "2016-08-18",
"genre_ids": [
10752,
35,
18
],
"id": 308266,
"original_title": "War Dogs",
"original_language": "en",
"title": "War Dogs",
"backdrop_path": "/2cLndRZy8e3das3vVaK3BdJfRIi.jpg",
"popularity": 5.186717,
"vote_count": 55,
"video": false,
"vote_average": 5.08
},
{
"poster_path": "/e9Rzr8Hhu3pqdJtdDLC52PerLk1.jpg",
"adult": false,
"overview": "Pete is a mysterious 10-year-old with no family and no home who claims to live in the woods with a giant, green dragon named Elliott. With the help of Natalie, an 11-year-old girl whose father Jack owns the local lumber mill, forest ranger Grace sets out to determine where Pete came from, where he belongs, and the truth about this dragon.",
"release_date": "2016-08-10",
"genre_ids": [
12,
10751,
14
],
"id": 294272,
"original_title": "Pete's Dragon",
"original_language": "en",
"title": "Pete's Dragon",
"backdrop_path": "/AaRhHX0Jfpju0O6hNzScPRgX9Mm.jpg",
"popularity": 4.93384,
"vote_count": 72,
"video": false,
"vote_average": 4.85
},
{
"poster_path": "/pXqnqw4V1Rly2HEacfl07d5DcUE.jpg",
"adult": false,
"overview": "59 year-old Ove is the block’s grumpy man. Several years ago he was deposed as president of the condominium association, but he could not give a damn about being deposed and therefore keeps looking over the neighborhood with an iron fist. When pregnant Parvaneh and her family move into the terraced house opposite Ove and accidentally back into Ove’s mailbox it sets off the beginning of an unexpected change in his life.",
"release_date": "2016-08-26",
"genre_ids": [
35,
18
],
"id": 348678,
"original_title": "En man som heter Ove",
"original_language": "sv",
"title": "A Man Called Ove",
"backdrop_path": "/o3PDMTyyMOGFNtze7YsfdWeMKpm.jpg",
"popularity": 4.790786,
"vote_count": 27,
"video": false,
"vote_average": 5.57
},
{
"poster_path": "/3Kr9CIIMcXTPlm6cdZ9y3QTe4Y7.jpg",
"adult": false,
"overview": "In the epic fantasy, scruffy, kindhearted Kubo ekes out a humble living while devotedly caring for his mother in their sleepy shoreside village. It is a quiet existence – until a spirit from the past catches up with him to enforce an age-old vendetta. Suddenly on the run from gods and monsters, Kubo’s chance for survival rests on finding the magical suit of armor once worn by his fallen father, the greatest samurai the world has ever known. Summoning courage, Kubo embarks on a thrilling odyssey as he faces his family’s history, navigates the elements, and bravely fights for the earth and the stars.",
"release_date": "2016-08-18",
"genre_ids": [
12,
16,
14,
10751
],
"id": 313297,
"original_title": "Kubo and the Two Strings",
"original_language": "en",
"title": "Kubo and the Two Strings",
"backdrop_path": "/akd0Z0OiR20btITvmvweDcJ3m8H.jpg",
"popularity": 4.572192,
"vote_count": 34,
"video": false,
"vote_average": 6.93
},
{
"poster_path": "/rxXA5vwJElXQ8BgrB0pocUcuqFA.jpg",
"adult": false,
"overview": "When Rebecca left home, she thought she left her childhood fears behind. Growing up, she was never really sure of what was and wasn’t real when the lights went out…and now her little brother, Martin, is experiencing the same unexplained and terrifying events that had once tested her sanity and threatened her safety. A frightening entity with a mysterious attachment to their mother, Sophie, has reemerged.",
"release_date": "2016-07-22",
"genre_ids": [
27
],
"id": 345911,
"original_title": "Lights Out",
"original_language": "en",
"title": "Lights Out",
"backdrop_path": "/mK9KdQj5Z6CAtxnFu2XPO8m78Il.jpg",
"popularity": 4.483865,
"vote_count": 133,
"video": false,
"vote_average": 6.11
},
{
"poster_path": "/3mCcVbVLz23MhCngELFihX2uSwb.jpg",
"adult": false,
"overview": "XOXO follows six strangers whose lives collide in one frenetic, dream-chasing, hopelessly romantic night.",
"release_date": "2016-08-26",
"genre_ids": [
18
],
"id": 352492,
"original_title": "XOXO",
"original_language": "en",
"title": "XOXO",
"backdrop_path": "/dP3bxMPEDc9eNN2nH9P5YyhS27p.jpg",
"popularity": 4.478293,
"vote_count": 4,
"video": false,
"vote_average": 7
},
{
"poster_path": "/zm0ODjtfJfJW0W269LqsQl5OhJ8.jpg",
"adult": false,
"overview": "As Batman hunts for the escaped Joker, the Clown Prince of Crime attacks the Gordon family to prove a diabolical point mirroring his own fall into madness. Based on the graphic novel by Alan Moore and Brian Bolland.",
"release_date": "2016-07-24",
"genre_ids": [
28,
16,
80,
18
],
"id": 382322,
"original_title": "Batman: The Killing Joke",
"original_language": "en",
"title": "Batman: The Killing Joke",
"backdrop_path": "/7AxMc1Mgm3xD2lySdM6r0sQGS3s.jpg",
"popularity": 4.136973,
"vote_count": 141,
"video": false,
"vote_average": 5.91
},
{
"poster_path": "/4J2Vc32juKTSdqm273HDKHsWO42.jpg",
"adult": false,
"overview": "A weekend getaway for four couples takes a sharp turn when one of the couples discovers the entire trip was orchestrated to host an intervention on their marriage.",
"release_date": "2016-08-26",
"genre_ids": [
35,
18
],
"id": 351242,
"original_title": "The Intervention",
"original_language": "en",
"title": "The Intervention",
"backdrop_path": "/xvghzVFYDJd26Txy2s0rHORrXIi.jpg",
"popularity": 4.113746,
"vote_count": 7,
"video": false,
"vote_average": 3.79
},
{
"poster_path": "/eZJYbODPWMRe6aQ1KtKHMb5ZOnx.jpg",
"adult": false,
"overview": "The adventures of teenager Max McGrath and alien companion Steel, who must harness and combine their tremendous new powers to evolve into the turbo-charged superhero Max Steel.",
"release_date": "2016-08-26",
"genre_ids": [
878,
28,
12
],
"id": 286567,
"original_title": "Max Steel",
"original_language": "en",
"title": "Max Steel",
"backdrop_path": "/9bM4Est3pyXPLr1vF2o5BiRtp0L.jpg",
"popularity": 3.541536,
"vote_count": 9,
"video": false,
"vote_average": 4.22
},
{
"poster_path": "/v0krYaMdqD9uxFuFiWhEyKKIaw5.jpg",
"adult": false,
"overview": "Elite snipers Brandon Beckett (Chad Michael Collins) and Richard Miller (Billy Zane) tasked with protecting a gas pipeline from terrorists looking to make a statement. When battles with the enemy lead to snipers being killed by a ghost shooter who knows their exact location, tensions boil as a security breach is suspected. Is there someone working with the enemy on the inside? Is the mission a front for other activity? Is the Colonel pulling the strings?",
"release_date": "2016-08-02",
"genre_ids": [
28,
18,
10752
],
"id": 407375,
"original_title": "Sniper: Ghost Shooter",
"original_language": "en",
"title": "Sniper: Ghost Shooter",
"backdrop_path": "/yYS8wtp7PgRcugt6EUMhv95NnaK.jpg",
"popularity": 3.504234,
"vote_count": 17,
"video": false,
"vote_average": 4.76
},
{
"poster_path": "/c4mvBk9cRAkyp9DpzlOBSmeuzG6.jpg",
"adult": false,
"overview": "Summer, New York City. A college girl falls hard for a guy she just met. After a night of partying goes wrong, she goes to wild extremes to get him back.",
"release_date": "2016-08-26",
"genre_ids": [
18
],
"id": 336011,
"original_title": "White Girl",
"original_language": "en",
"title": "White Girl",
"backdrop_path": "/dxUxtnxeMsI0jCUFAT6GbgyUdiz.jpg",
"popularity": 3.485193,
"vote_count": 8,
"video": false,
"vote_average": 1.88
},
{
"poster_path": "/1SWIUZp4Gi2B6VxajpPWKhkbTMF.jpg",
"adult": false,
"overview": "The legendary Roberto Duran and his equally legendary trainer Ray Arcel change each other's lives.",
"release_date": "2016-08-26",
"genre_ids": [
18
],
"id": 184341,
"original_title": "Hands of Stone",
"original_language": "en",
"title": "Hands of Stone",
"backdrop_path": "/pqRJD5RE5DgRQ1Mq4kSZHmMjozn.jpg",
"popularity": 3.474028,
"vote_count": 16,
"video": false,
"vote_average": 3.75
}
],
"dates": {
"maximum": "2016-09-01",
"minimum": "2016-07-21"
},
"total_pages": 33,
"total_results": 649
}
You see that it is wrapped in a results object. So, you would need something like this:
public class RootObject
{
public NowPlaying[] Results { get; set; }
}
And deserialize into the RootObject. A great tool to get the structure of a JSON object is quicktype.io. Or, it is even baked into Visual Studio (Windows only). Copy your JSON object and go to Edit > Paste special > Paste JSON as Classes
I'm making a call to a Wikipedia API which returns a Title, Shot-Text, Image and geo-cordinates of that location. My Wikipedia API is:
https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts|pageimages|coordinates&titles=Berlin&redirects=1&formatversion=2&exintro=1&explaintext=1&piprop=thumbnail&pithumbsize=400
Also I'm using another Wikipedia API which returns a list of place names according to their geo-coordinates:
https://en.wikipedia.org/w/api.php?format=json&action=query&list=geosearch&gsradius=1000&gscoord=52.5243700|13.4105300&gslimit=50&gsprop=type|dim|globe
For the second API I get a response like this:
"query": {
"geosearch": [
{
"pageid": 28782169,
"ns": 0,
"title": "1757 Berlin raid",
"lat": 52.523405,
"lon": 13.4114,
"dist": 122.4,
"primary": "",
"type": null,
"dim": 1000
},
{
"pageid": 526195,
"ns": 0,
"title": "Scheunenviertel",
"lat": 52.526111111111,
"lon": 13.41,
"dist": 196.9,
"primary": "",
"type": "landmark",
"dim": 1000
},
...
]
}
Now I want to combine these two searches in one API. I want to add information from my first API within the second API, something like as below:
"query": {
"geosearch": [
{
"pageid": 28782169,
"ns": 0,
"title": "1757 Berlin raid",
"lat": 52.523405,
"lon": 13.4114,
"dist": 122.4,
"primary": "",
"type": null,
"dim": 1000
"pages": [
{
"pageid": 28782169,
"ns": 0,
"title": "1757 Berlin raid",
"extract": "Berlin is the capital of Germany and one of the 16 states of Germany. With a population of 3.5 million people, it is the second most populous city proper and the seventh.........",
"thumbnail": {
"source": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Siegessaeule_Aussicht_10-13_img4_Tiergarten.jpg/400px-Siegessaeule_Aussicht_10-13_img4_Tiergarten.jpg",
"width": 400,
"height": 267
}
}
]
},
...
]
}
I want to know is it possible in this way?
So, if I understand right, you want with one Wikipedia API request to get a title, shot-text, image and geo-cordinates (your first API) for all the places (Wikipedia articles) that are located in a certain area by given coordinates and radius (your second API). If that correct, you can do it in this way:
main parameters: format=json&action=query
query parameters:
redirects=1
generator=geosearch (your second API: see point 3)
prop=extracts|coordinates|pageimages (your first API: see point 4, 5, and 6)
geosearch parameters (all generator parameters are prefixed with a "g"):
ggslimit=20 - your total results from the query (because the exlimit=20)
ggsradius=1000&ggscoord=52.5243700|13.4105300 - this is your entry point
parameters for extracts: exintro=1&explaintext=1&exlimit=20 (max exlimit is 20)
parameters for coordinates: coprop=type|dim|globe&colimit=20 (max colimit is 500)
parameters for pageimages: piprop=thumbnail&pithumbsize=400&pilimit=20 (max is 50)
How you see, the max colimit is 500 and the max pilimit is 50, but we can't use more than 20, because of exlimit.
Or finally, your request will be the joining of all parameters above:
https://en.wikipedia.org/w/api.php?format=json&action=query&redirects=1&generator=geosearch&prop=extracts|coordinates|pageimages&ggslimit=20&ggsradius=1000&ggscoord=52.5243700|13.4105300&exintro=1&explaintext=1&exlimit=20&coprop=type|dim|globe&colimit=20&piprop=thumbnail&pithumbsize=400&pilimit=20
And here is the response:
"query":{
"pages":{
"2511":{
"pageid":2511,
"ns":0,
"title":"Alexanderplatz",
"extract":"Alexanderplatz (pronounced [\u0294al\u025bk\u02c8sand\u0250\u02ccplats]) is a large public square and transport hub in the central Mitte district of Berlin, near the Fernsehturm. Berliners often call it simply Alex, referring to a larger neighbourhood stretching from Mollstra\u00dfe in the northeast to Spandauer Stra\u00dfe and the Rotes Rathaus in the southwest.",
"coordinates":[
{
"lat":52.52166748,
"lon":13.41333294,
"primary":"",
"type":"landmark",
"dim":"1000",
"globe":"earth"
}
],
"thumbnail":{
"source":"https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Alexanderplatz_by_the_night_-_ProtoplasmaKid.webm/400px--Alexanderplatz_by_the_night_-_ProtoplasmaKid.webm.jpg",
"width":400,
"height":225
}
},
...
},
}
Consider a social network. It has posts. For feed, you request /feed and get the list of posts.
In the UI, there are things to show for a post, like if the user liked the post or not, if the user starred it or not, etc. These things don't look like they belong inside the post object.
Another case is when you fetch the likes. The frontend needs to know if the user in each 'like' object is being followed or not.
Where to put this info in the response JSON?
Its depends on your application and which data you want to show to the user. For ex,consider you are listing a user's feeds. In that feed,you want to show
Message
Liked by the current user or not(i don't know the difference between liked and stared)
Number of likes
List of liked users.
shared by the user or not
Shared count
List ofShared users.
etc..
In the above list,
Some data need two api fetch to get complete info and some not. For example,"List of liked users","List of Shared users". This is generally a dynamic data module. You have to get those details in a separate api for better performance of the server and also data integrity.
In some cases,some apps needs sneak peek of the liked shared users info in the listing page. In that case,you can include the some fixed small number of users details in the same list /feeds response itself and include the "See More(like Facebook)" option in the UI.
Some static singular data(single column data) can be list in the initial get /feeds itself.
I wonder why don't you follow the same twitter's list tweets style,
https://dev.twitter.com/rest/reference/get/search/tweets
{
"coordinates": null,
"favorited": false,
"truncated": false,
"created_at": "Fri Sep 21 23:40:54 +0000 2012",
"id_str": "249292149810667520",
"entities": {
"urls": [
],
"hashtags": [
{
"text": "FreeBandNames",
"indices": [
20,
34
]
}
],
"user_mentions": [
]
},
"in_reply_to_user_id_str": null,
"contributors": null,
"text": "Thee Namaste Nerdz. #FreeBandNames",
"metadata": {
"iso_language_code": "pl",
"result_type": "recent"
},
"retweet_count": 0,
"in_reply_to_status_id_str": null,
"id": 249292149810667520,
"geo": null,
"retweeted": false,
"in_reply_to_user_id": null,
"place": null,
"user":
{
"profile_sidebar_fill_color": "DDFFCC",
"profile_sidebar_border_color": "BDDCAD",
"profile_background_tile": true,
"name": "Chaz Martenstein",
"profile_image_url": "http://a0.twimg.com/profile_images/447958234/Lichtenstein_normal.jpg",
"created_at": "Tue Apr 07 19:05:07 +0000 2009",
"location": "Durham, NC",
"follow_request_sent": null,
"profile_link_color": "0084B4",
"is_translator": false,
"id_str": "29516238",
"entities": {
"url": {
"urls": [
{
"expanded_url": null,
"url": "http://bullcityrecords.com/wnng/",
"indices": [
0,
32
]
}
]
},
"description": {
"urls": [
]
}
},
"default_profile": false,
"contributors_enabled": false,
"favourites_count": 8,
"url": "http://bullcityrecords.com/wnng/",
"profile_image_url_https": "https://si0.twimg.com/profile_images/447958234/Lichtenstein_normal.jpg",
"utc_offset": -18000,
"id": 29516238,
"profile_use_background_image": true,
"listed_count": 118,
"profile_text_color": "333333",
"lang": "en",
"followers_count": 2052,
"protected": false,
"notifications": null,
"profile_background_image_url_https": "https://si0.twimg.com/profile_background_images/9423277/background_tile.bmp",
"profile_background_color": "9AE4E8",
"verified": false,
"geo_enabled": false,
"time_zone": "Eastern Time (US & Canada)",
"description": "You will come to Durham, North Carolina. I will sell you some records then, here in Durham, North Carolina. Fun will happen.",
"default_profile_image": false,
"profile_background_image_url": "http://a0.twimg.com/profile_background_images/9423277/background_tile.bmp",
"statuses_count": 7579,
"friends_count": 348,
"following": null,
"show_all_inline_media": true,
"screen_name": "bullcityrecords"
},
"in_reply_to_screen_name": null,
"source": "web",
"in_reply_to_status_id": null
}
You have two options:
Make a separate API method for getting information about user context data - /api/users/1/feeds/1 .Pay attention, that this option will force you to send request per feed. So, if you'll have 1000 feeds - you will have 1000 + 1 request (so called N+1 problem).
As for me - it's not a good idea.
You can store user data in json, for example is this way:
{
"feedName": "feed1",
...
"currentUser": {
"liked": true,
"starred": true
}
}
By using this option you will avoid N+1 requests problem in your RESTful service
For all the users, the post resource should be the same. Adding specific user context info inside it seems like polluting it
I can see where you're coming from and I quite agree.
Ivan's 1st solution should not be used as he already mentioned, his 2nd is better but then if you GET the posts JSON which should contain only post objects, there is also this currentUser that doesn't really belong there.
My suggestion is that for each post you keep track of which users have liked and/or starred it, etc. Then you keep a clean structure while still having the info you need available in the same request/response.
Example
GET /feed HTTP/1.1
[
{
"text": "hello world, im a post!",
"author": "Jack",
"likes": 3,
"likedBy": [
"John",
"James",
"Jessica"
],
"stars": 2,
"starredBy": [
"John",
"Mary"
]
},
{
"text": "hello world, im also a post! :D",
"author": "Mary",
"likes": 1,
"likedBy": [
"James"
],
"stars": 0,
"starredBy": [
]
},
]
Where each {} object represents a post object.
On the client side, you could then check if the likedBy list contains the currently logged in user and proceed with the result as you see fit. Same for stars and any other of these properties a post might have.
I think that's the word I'm looking for. I'm trying to get parent info into each of the cards. I think that's what I need to do, but chime in if you have any other ideas.
{
"LEA": {
"name": "Limited Edition Alpha",
"code": "LEA",
"releaseDate": "1993-08-05",
"border": "black",
"type": "core",
"cards": [
{"name": "Air Elemental"},
{"name": "Earth Elemental"},
{"name": "Fire Elemental"},
{"name": "Water Elemental"}
]
},
"LEB": {
"name": "Limited Edition Beta",
"code": "LEB",
"releaseDate": "1993-10-01",
"border": "black",
"type": "core",
"cards": [
{"name": "Armageddon"},
{"name": "Fireball"},
{"name": "Swords to Plowshares"},
{"name": "Wrath of God"}
]
}
}
This is a tiny subset of the data, obviously. LEA and LEB are sets of cards, and inside each set there are a bunch of cards. I'm thinking of denormalizing this into just the cards, with the set info added to each card. Something like this...
{
{
"name": "Air Elemental",
"set": {
"name": "Limited Edition Alpha",
"code": "LEA",
"releaseDate": "1993-08-05",
"border": "black",
"type": "core"
}
},
{
"name": "Earth Elemental",
"set": {
"name": "Limited Edition Alpha",
"code": "LEA",
"releaseDate": "1993-08-05",
"border": "black",
"type": "core"
}
},
{
"name": "Armageddon",
"set": {
"name": "Limited Edition Beta",
"code": "LEB",
"releaseDate": "1993-10-01",
"border": "black",
"type": "core"
}
},
{
"name": "Fireball",
"set": {
"name": "Limited Edition Beta",
"code": "LEB",
"releaseDate": "1993-10-01",
"border": "black",
"type": "core"
}
}
}
Is my thinking right, first and foremost? Would I want a giant collection of cards and have the set information flattened into each card? In SQL, I'd do a table for the sets, and and the cards would belong_to a set. I'm trying to wrap my head around 'document thinking'.
Second, if my thinking is correct, any ideas on how I could achieve this denormalizing?
Here you go =).
OK here is where I would start. Since we've said that cards will never change (since they're based on physical MTG cards), create one collection with all of your cards in it, this will be used for easily populating a user's deck later on. You can search on it by card name or some sort of card ID (like a physical one, stored on the card).
For the user's array of card objects, you shouldn't just store the _id field for a card, because that forces you to join. Since cards will never change, completely denormalize them and just shove them in that card array, so a user object, so far, resembles:
{
name: "Tom Hanks",
skill_level: 0,
decks: [
[
{
card_name: "Balance",
card_description: "LONG_BLOCK_OF_DESCRIP_TEXT",
card_creator: "Sugargirl14",
type: "Normal",
_id: $SOME_MONGO_ID_HERE,
... rest of card data...
}, {
...card 2 complete data...
}
],
[
{ ...another deck here... }
]
]
}
OK, back to set info, I will also assume set info is a constant (based on your SO post, I can't see how it would physically change). So, if that set info is always relevant to the card, I would denormalize and include it, changing our card object to:
{
card_name: "Balance",
card_description: "LONG_BLOCK_OF_DESCRIP_TEXT",
card_creator: "Sugargirl14",
type: "Normal",
_id: $SOME_MONGO_ID_HERE
set: {
"name": "Limited Edition Alpha",
"code": "LEA",
"releaseDate": "1993-08-05",
"border": "black",
"type": "core",
"_id": $SOME_MONGO_ID_HERE
},
... rest of card data...
}
I imagine that storing the other cards in the denormalized object for a given card isn't relevant, if it is, add them. If you'll note, the key that is given in your SO example is dropped, since it seems to always == the "code" field.
OK, now to properly answer your SO question about whether you should embed sets in cards, or vice versa. First off, both collections are relevant. So, even if we embed sets into cards, you'll want those sets in a collection so they can be fetched later and inserted into new cards.
Which gets embedded in which is really determined by business logic, how the data is used and which gets pulled more often. Are you frequently displaying sets and pulling cards from them (like for users to search)? You could embed all of the card data, or any relevant data, in each set's cards array. But with the above data model, each card stores its set ID in its set object. I assume cards belong to only one set, so to get all cards for a set you can query over your card collection where set.id == the Mongo ID of the set you want. Now sets need minimal updates, due to business logic, (hopefully none at all) and your queries are still fast (and you get complete card objects). I'd, honestly, do that latter one and keep my sets clean of cards. As such, a card owns the set it belongs to as opposed to a set owning a card. That's a more SQLy way to think that actually can work fine in Mongo (you'll never join).
So our final data model resembles:
Collection 1, Set:
//data model
{
"name": "Limited Edition Alpha",
"code": "LEA",
"releaseDate": "1993-08-05",
"border": "black",
"type": "core",
"_id": $SOME_MONGO_ID_HERE
}
Collection 2, cards:
//data model
{
_id: $SOME_MONGO_ID_HERE
card_name: "Balance",
card_description: "LONG_BLOCK_OF_DESCRIP_TEXT",
card_creator: "Sugargirl14",
type: "Normal",
set: {
"name": "Limited Edition Alpha",
"code": "LEA",
"releaseDate": "1993-08-05",
"border": "black",
"type": "core",
"_id": $SOME_MONGO_ID_HERE
... rest of card data...
},
}
Collection 3, users:
{
_id: $SOME_MONGO_ID_HERE,
name: "Tom Hanks",
skill_level: 0,
decks: [
[
{
card_name: "Balance",
card_description: "LONG_BLOCK_OF_DESCRIP_TEXT",
card_creator: "Sugargirl14",
type: "Normal",
_id: $SOME_MONGO_ID_HERE,
set: {
"name": "Limited Edition Alpha",
"code": "LEA",
"releaseDate": "1993-08-05",
"border": "black",
"type": "core",
"_id": $SOME_MONGO_ID_HERE
},
}, {
...card 2 complete data...
}
],
[
{ ...another deck here... }
]
]
}
This, obviously, assumes set data for each card is relevant to the user. Now your data is denormalized, sets and cards rarely need updates (according to business logic), so you'll never need cascading updates or deletes. Manipulating users is easy. When you remove a card from a user's deck you can do a $pull from Mongo (I think that's what it's called) on the relevant decks array where a contained item's _id field == the Mongo ID of the card you want to remove. All other updates are easier.
In retrospect, you might want to make the user's decks like so:
decks: {
"SOME_ID_HERE": [
{ ...card 1... },
{ ...card 2... }
]
}
This makes identifying the decks MUCH easier and will make your pulls easier (you'll have more data on the frontend and the pull query will be more precise). It can be a number, random string, anything really, since it gets passed back to the frontend. Or just use their Mongo ID, when looking at a deck, a user will have it's Mongo ID. Then when they pull a card out of it, or add one in, you have a direct identifier to easily grab the deck needed.
Obviously all values with text like: $MONGO_ID_HERE should really be MongoId() objects.
Whew, that was intense, 6800 characters. Hope it makes sense to you and I apologize if any verbiage is confusing or if any of my JSON objects' formatting is fucked up (just let me know if any prose is confusing, I'll reword). Does this make sense/solve your problem?